1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-21 08:57:38 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
SquidDev
8819f2559d Add some tests for the new executor system
And fix a couple of bugs picked up by said tests
2018-09-18 18:21:00 +01:00
SquidDev
4b741739e8 Update the executor to work with the new calling system 2018-09-13 10:32:45 +01:00
SquidDev
f23acef2dd Merge remote-tracking branch 'SquidDev-CC-ComputerCraft/feature/method-future' into feature/onethread-cobalt 2018-09-09 22:13:45 +01:00
SquidDev
ac8444b364 Migrate ComputerCraft's peripherals/APIs to use the non-blocking API
This is definitely a little ugly in places, mostly due to how we've
handled backwards compatibility.
2018-09-09 17:06:34 +01:00
SquidDev
3b4c1eac1c Proposal for replacing ILuaContext with a non-blocking alternative
This is an initial prototype for ways we could better integrate Lua's
asynchronous functionality (coroutines) with peripherals and other
Lua APIs.

The existing system assumes that coroutines each have a backing thread,
and so yields will suspect the current thread. This takes inspiration
from various "promise" libraries - asynchronous operations (such as
executing on the main thread or waiting for an event) return a promise -
the result of which can be consumed with `.then`.

While I am not aware of plans to change the Lua implementation, this
does give us greater flexibility in the future, leading the way for
Rembulan, single-threaded Cobalt, and even possibly OC support.
2018-09-09 17:04:07 +01:00
SquidDev
7cc77cb1ed Add a basic implementation of the single-thread Lua 2018-09-01 18:40:10 +01:00
SquidDev
0f70d68d0d A minor refactor to computer method calls
We move several rather complex classes into a separate file.
2018-09-01 17:34:26 +01:00
101 changed files with 3885 additions and 2924 deletions

12
.gitignore vendored
View File

@@ -1,12 +1,12 @@
/build build
/out out
/run run
deploy
*.ipr *.ipr
*.iws *.iws
*.iml *.iml
.idea .idea
.gradle .gradle
/luaj-2.0.3/lib luaj-2.0.3/lib
/luaj-2.0.3/*.jar luaj-2.0.3/*.jar
*.DS_Store *.DS_Store
/test-files

View File

@@ -28,8 +28,6 @@ to see the full changes, but here's a couple of the more interesting changes:
computers remotely. computers remotely.
- Add full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is - Add full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is
installed). installed).
- Extended binary file handles. They support file seeking, and reading new lines, allowing full (and accurate)
emulation of the standard Lua `io` library.
## Relation to CCTweaks? ## Relation to CCTweaks?
This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. That being said, This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. That being said,
@@ -38,7 +36,7 @@ computers.
## Contributing ## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute
code, do consider submitting it to the ComputerCraft repository first. code, do consider submitting it to the ComputerCraft repository instead.
That being said, in order to start helping develop CC:T, you'll need to follow these steps: That being said, in order to start helping develop CC:T, you'll need to follow these steps:

View File

@@ -23,7 +23,7 @@ apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'maven' apply plugin: 'maven'
version = "1.80pr1.11" version = "1.80pr1.8"
group = "org.squiddev" group = "org.squiddev"
archivesBaseName = "cc-tweaked" archivesBaseName = "cc-tweaked"
@@ -42,6 +42,7 @@ minecraft {
} }
repositories { repositories {
mavenLocal()
maven { maven {
name = "JEI" name = "JEI"
url = "http://dvs1.progwml6.com/files/maven" url = "http://dvs1.progwml6.com/files/maven"
@@ -66,7 +67,7 @@ dependencies {
runtime "mezz.jei:jei_1.12.2:4.8.5.159" runtime "mezz.jei:jei_1.12.2:4.8.5.159"
shade 'org.squiddev:Cobalt:0.4.0' shade 'org.squiddev:Cobalt:0.3.2-nothread'
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
@@ -131,7 +132,7 @@ curseforge {
project { project {
id = '282001' id = '282001'
releaseType = 'beta' releaseType = 'beta'
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${project.version})." changelog = ''
} }
} }
@@ -196,3 +197,9 @@ gradle.projectsEvaluated {
runClient.outputs.upToDateWhen { false } runClient.outputs.upToDateWhen { false }
runServer.outputs.upToDateWhen { false } runServer.outputs.upToDateWhen { false }
test {
testLogging {
events "failed", "standardOut", "standardError"
}
}

12
build_luaj.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
cd luaj-2.0.3
echo "Building LuaJ..."
ant clean
ant
echo "Copying output to libs..."
rm ../libs/luaj-jse-2.0.3.jar
cp luaj-jse-2.0.3.jar ../libs
echo "Done."
cd ..

10
codesize.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
echo "Java code:"
cat `find src | grep \\.java$` | wc
echo "Lua code:"
cat `find src/main/resources/assets/computercraft/lua | grep \\.lua$` | wc
echo "JSON:"
cat `find src/main/resources/assets/computercraft | grep \\.json$` | wc

21
deploy.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
echo "Building with gradle..."
rm -rf build/libs
rm -rf build/resources
rm -rf build/classes
chmod -R +rw src/main/resources
chmod +x gradlew
./gradlew build
echo "Deleting old deployment..."
rm -rf deploy
mkdir deploy
echo "Making new deployment..."
INPUTJAR=`ls -1 build/libs | grep -v sources`
OUTPUTJAR=`ls -1 build/libs | grep -v sources | sed s/\-//g`
FRIENDLYNAME=`ls -1 build/libs | grep -v sources | sed s/\-/\ /g | sed s/\.jar//g`
cp build/libs/$INPUTJAR deploy/$OUTPUTJAR
echo "Done."

BIN
libs/luaj-jse-2.0.3.jar Normal file

Binary file not shown.

7
setup.bat Normal file
View File

@@ -0,0 +1,7 @@
echo "Setting up IntelliJ development environment with gradle..."
rmdir /s /q .\build
call gradlew.bat --stacktrace setupDecompWorkspace --refresh-dependencies
call gradlew.bat --stacktrace cleanIdea idea
echo "Done."
pause

14
setup.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
echo "Setting permissions..."
chmod +x codesize.sh
chmod +x build_luaj.sh
chmod +x deploy.sh
chmod +x gradlew
echo "Setting up IntelliJ development environment with gradle..."
rm -rf build
./gradlew --stacktrace setupDecompWorkspace --refresh-dependencies
./gradlew --stacktrace cleanIdea idea
echo "Done."

View File

@@ -26,9 +26,11 @@ import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate; import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.filesystem.ComboMount; import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount; import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.FileSystemMount; import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputer;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer; import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
import dan200.computercraft.shared.computer.blocks.BlockComputer; import dan200.computercraft.shared.computer.blocks.BlockComputer;
@@ -58,10 +60,7 @@ import dan200.computercraft.shared.proxy.IComputerCraftProxy;
import dan200.computercraft.shared.turtle.blocks.BlockTurtle; import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.CreativeTabMain; import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
import dan200.computercraft.shared.wired.CapabilityWiredElement; import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode; import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@@ -98,8 +97,6 @@ import java.io.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@@ -111,12 +108,12 @@ import java.util.zip.ZipFile;
@Mod( @Mod(
modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}", modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory", guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory"
dependencies = "required:forge@[14.23.4.2746,)"
) )
public class ComputerCraft public class ComputerCraft
{ {
public static final String MOD_ID = "computercraft"; public static final String MOD_ID = "computercraft";
public static final String LOWER_ID = "computercraft";
// GUI IDs // GUI IDs
public static final int diskDriveGUIID = 100; public static final int diskDriveGUIID = 100;
@@ -570,7 +567,7 @@ public class ComputerCraft
width = terminal.getWidth(); width = terminal.getWidth();
height = terminal.getHeight(); height = terminal.getHeight();
} }
// Pack useful terminal information into the various coordinate bits. // Pack useful terminal information into the various coordinate bits.
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement // These are extracted in ComputerCraftProxyCommon.getClientGuiElement
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(), player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
@@ -925,12 +922,11 @@ public class ComputerCraft
{ {
try try
{ {
FileSystem fs = FileSystems.newFileSystem( modJar.toPath(), ComputerCraft.class.getClassLoader() ); IMount jarMount = new JarMount( modJar, subPath );
mounts.add( new FileSystemMount( fs, subPath ) ); mounts.add( jarMount );
} }
catch( IOException | RuntimeException | ServiceConfigurationError e ) catch( IOException e )
{ {
ComputerCraft.log.error( "Could not load mount from mod jar", e );
// Ignore // Ignore
} }
} }
@@ -940,16 +936,16 @@ public class ComputerCraft
if( resourcePackDir.exists() && resourcePackDir.isDirectory() ) if( resourcePackDir.exists() && resourcePackDir.isDirectory() )
{ {
String[] resourcePacks = resourcePackDir.list(); String[] resourcePacks = resourcePackDir.list();
for( String resourcePackName : resourcePacks ) for( String resourcePack1 : resourcePacks )
{ {
try try
{ {
File resourcePack = new File( resourcePackDir, resourcePackName ); File resourcePack = new File( resourcePackDir, resourcePack1 );
if( !resourcePack.isDirectory() ) if( !resourcePack.isDirectory() )
{ {
// Mount a resource pack from a jar // Mount a resource pack from a jar
FileSystem fs = FileSystems.newFileSystem( resourcePack.toPath(), ComputerCraft.class.getClassLoader() ); IMount resourcePackMount = new JarMount( resourcePack, subPath );
mounts.add( new FileSystemMount( fs, subPath ) ); mounts.add( resourcePackMount );
} }
else else
{ {
@@ -962,9 +958,9 @@ public class ComputerCraft
} }
} }
} }
catch( IOException | RuntimeException | ServiceConfigurationError e ) catch( IOException e )
{ {
ComputerCraft.log.error( "Could not load resource pack '" + resourcePackName + "'", e ); // Ignore
} }
} }
} }

View File

@@ -13,8 +13,6 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.List; import java.util.List;
/** /**
@@ -74,25 +72,7 @@ public interface IMount
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram". * @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream representing the contents of the file. * @return A stream representing the contents of the file.
* @throws IOException If the file does not exist, or could not be opened. * @throws IOException If the file does not exist, or could not be opened.
* @deprecated Use {@link #openChannelForRead(String)} instead
*/ */
@Nonnull @Nonnull
@Deprecated
InputStream openForRead( @Nonnull String path ) throws IOException; InputStream openForRead( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A channel representing the contents of the file. If the channel implements
* {@link java.nio.channels.SeekableByteChannel}, one will be able to seek to arbitrary positions when using binary
* mode.
* @throws IOException If the file does not exist, or could not be opened.
*/
@Nonnull
@SuppressWarnings("deprecation")
default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForRead( path ) );
}
} }

View File

@@ -13,8 +13,6 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
/** /**
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)} * Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
@@ -52,54 +50,20 @@ public interface IWritableMount extends IMount
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram". * @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to * @return A stream for writing to
* @throws IOException If the file could not be opened for writing. * @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForWrite(String)} instead.
*/ */
@Nonnull @Nonnull
@Deprecated
OutputStream openForWrite( @Nonnull String path ) throws IOException; OutputStream openForWrite( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
* will be able to seek to arbitrary positions when using binary mode.
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
@SuppressWarnings("deprecation")
default WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForWrite( path ) );
}
/** /**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it. * Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
* *
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram". * @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to. * @return A stream for writing to.
* @throws IOException If the file could not be opened for writing. * @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForAppend(String)} instead.
*/ */
@Nonnull @Nonnull
@Deprecated
OutputStream openForAppend( @Nonnull String path ) throws IOException; OutputStream openForAppend( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
* will be able to seek to arbitrary positions when using binary mode.
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
@SuppressWarnings("deprecation")
default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForAppend( path ) );
}
/** /**
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the * Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
* mount, and write operations should fail once it reaches zero. * mount, and write operations should fail once it reaches zero.

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods that allow the
* method to interact with the invoking computer.
*/
public interface ICallContext
{
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link MethodResult#onMainThread(ILuaCallable)}.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* A function which calls performs an action in a specific context (such as on the server thread) and returns a result.
*
* @see MethodResult#onMainThread(ILuaCallable)
* @see ILuaContext#executeMainThreadTask(ILuaTask)
*/
@FunctionalInterface
public interface ILuaCallable
{
/**
* Run the code within the specified context and return the result to continue with.
*
* @return The result of executing this function. Note that this may not be evaluated within the same context as
* this call is.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
*/
@Nonnull
MethodResult execute() throws LuaException;
}

View File

@@ -1,6 +1,6 @@
/* /*
* This file is part of the public ComputerCraft API - http://www.computercraft.info * This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. * Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info. * For help using the API, and posting your mods, visit the forums at computercraft.info.
*/ */
@@ -13,8 +13,11 @@ import javax.annotation.Nullable;
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods * An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
* that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need * that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
* to signal work to be performed on the main thread, and don't want to return until the work has been completed. * to signal work to be performed on the main thread, and don't want to return until the work has been completed.
*
* This interface mostly exists for integrating with older code. One should use {@link MethodResult} instead, as this
* encourages an asynchronous way of interacting with Lua coroutines.
*/ */
public interface ILuaContext public interface ILuaContext extends ICallContext
{ {
/** /**
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly * Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
@@ -30,8 +33,10 @@ public interface ILuaContext
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an * @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or * event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @deprecated Use {@link MethodResult#pullEvent(String, ILuaFunction)}
*/ */
@Nonnull @Nonnull
@Deprecated
Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException; Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException;
/** /**
@@ -45,8 +50,10 @@ public interface ILuaContext
* an event, InterruptedException will be thrown. This exception must not be caught or * an event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String) * @see #pullEvent(String)
* @deprecated Use {@link MethodResult#pullEventRaw(String, ILuaFunction)}
*/ */
@Nonnull @Nonnull
@Deprecated
Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException; Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException;
/** /**
@@ -59,8 +66,10 @@ public interface ILuaContext
* InterruptedException will be thrown. This exception must not be caught or * InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String) * @see #pullEvent(String)
* @deprecated Use {@link MethodResult#pullEventRaw(ILuaFunction)}
*/ */
@Nonnull @Nonnull
@Deprecated
Object[] yield( @Nullable Object[] arguments ) throws InterruptedException; Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
/** /**
@@ -76,22 +85,9 @@ public interface ILuaContext
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended, * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or * InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @deprecated Use {@link MethodResult#onMainThread(ILuaCallable)}
*/ */
@Nullable @Nullable
@Deprecated
Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException; Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link #executeMainThreadTask(ILuaTask)}.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
} }

View File

@@ -0,0 +1,24 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A function which executes using a {@link ILuaContext}.
*
* Like {@link ILuaContext}, this is not intended for use in the future - it purely exists as an argument for
* {@link MethodResult#withLuaContext(ILuaContextTask)}.
*/
@FunctionalInterface
public interface ILuaContextTask
{
@Nullable
@Deprecated
Object[] execute( @Nonnull ILuaContext context ) throws LuaException, InterruptedException;
}

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A Lua function which consumes some values and returns a result.
*
* @see MethodResult#then(ILuaFunction)
* @see MethodResult#pullEvent(ILuaFunction)
* @see MethodResult#pullEventRaw(String)
*/
@FunctionalInterface
public interface ILuaFunction
{
/**
* Accept the values and return another method result.
*
* @param values The inputs for this function.
* @return The result of executing this function.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
*/
@Nonnull
MethodResult call( @Nullable Object[] values ) throws LuaException;
}

View File

@@ -1,6 +1,6 @@
/* /*
* This file is part of the public ComputerCraft API - http://www.computercraft.info * This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. * Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info. * For help using the API, and posting your mods, visit the forums at computercraft.info.
*/ */
@@ -41,16 +41,45 @@ public interface ILuaObject
* wishes to call. The integer indicates the index into the getMethodNames() table * wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call() * that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} * @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* the possible values and conversion rules. * for the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program. * @return An array of objects, representing the values you wish to return to the Lua program. See
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and * {@link MethodResult#of(Object...)} for the valid values and conversion rules.
* conversion rules. * @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* @throws LuaException If the task could not be queued, or if the task threw an exception. * same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended, * @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or * InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.w * intercepted, or the computer will leak memory and end up in a broken state.
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[]) * @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
* @deprecated Use {@link #callMethod(ICallContext, int, Object[])} instead.
*/ */
@Nullable @Nullable
@Deprecated
Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException; Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* Called when a user calls one of the methods that this object implements. This works the same as
* {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}}. See that method for detailed
* documentation.
*
* @param context The context of the current call.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}
* for the possible values and conversion rules.
* @return The result of calling this method. Use {@link MethodResult#empty()} to return nothing or
* {@link MethodResult#of(Object...)} to return several values.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])
* @see MethodResult
*/
@Nonnull
@SuppressWarnings( { "deprecation" } )
default MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( lua -> callMethod( lua, method, arguments ) );
}
} }

View File

@@ -0,0 +1,105 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Evaluates {@link MethodResult}s within a {@link ILuaContext}.
*
* @see MethodResult#evaluate(ILuaContext)
* @see MethodResult#withLuaContext(ILuaContextTask)
* @deprecated This should not be used except to interface between the two call call systems.
*/
@Deprecated
class LuaContextResultEvaluator
{
@Deprecated
public static Object[] evaluate( @Nonnull ILuaContext context, @Nonnull MethodResult future ) throws LuaException, InterruptedException
{
Deque<ILuaFunction> callbacks = null;
while( true )
{
if( future instanceof MethodResult.AndThen )
{
MethodResult.AndThen then = ((MethodResult.AndThen) future);
// Thens are "unwrapped", being pushed onto a stack
if( callbacks == null ) callbacks = new ArrayDeque<>();
callbacks.addLast( then.getCallback() );
future = then.getPrevious();
if( future == null ) throw new NullPointerException( "Null result from " + then.getCallback() );
}
else if( future instanceof MethodResult.Immediate )
{
Object[] values = ((MethodResult.Immediate) future).getResult();
// Immediate values values will attempt to call the previous "then", or return if nothing
// else needs to be done.
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
// Poll for an event, and then call the previous "then" or return if nothing else needs
// to be done.
Object[] values = onEvent.isRaw() ? context.pullEventRaw( onEvent.getFilter() ) : context.pullEvent( onEvent.getFilter() );
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else if( future instanceof MethodResult.OnMainThread )
{
MethodResult.OnMainThread onMainThread = (MethodResult.OnMainThread) future;
// Evaluate our task on the main thread and mark it as the next future to evaluate.
Reference temporary = new Reference();
context.executeMainThreadTask( () -> {
temporary.value = onMainThread.getTask().execute();
return null;
} );
future = temporary.value;
if( future == null ) throw new NullPointerException( "Null result from " + onMainThread.getTask() );
}
else if( future instanceof MethodResult.WithLuaContext )
{
MethodResult.WithLuaContext withContext = (MethodResult.WithLuaContext) future;
// Run the task, and then call the previous "then" or return if nothing else
// needs to be done.
Object[] values = withContext.getConsumer().execute( context );
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else
{
throw new IllegalStateException( "Unknown MethodResult " + future );
}
}
}
private static class Reference
{
MethodResult value;
}
}

View File

@@ -0,0 +1,354 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* The result of calling a method, such as {@link ILuaObject#callMethod(ICallContext, int, Object[])} or
* {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}.
*
* This is non-dissimilar to a promise or {@link ListenableFuture}. One can either return an immediate value through
* {@link #of(Object...)}, wait for an external action with {@link #onMainThread(ILuaCallable)} or {@link #pullEvent()}
* and then act on the result of either of those by using {@link #then(ILuaFunction)}.
*/
public abstract class MethodResult
{
private static MethodResult empty;
MethodResult()
{
}
/**
* A result which returns immediately with no value.
*
* Use {@link #of(Object...)} if you need to return one or more values.
*
* @return The empty method result.
* @see #of(Object...)
*/
@Nonnull
public static MethodResult empty()
{
if( empty == null ) empty = new Immediate( null );
return empty;
}
/**
* A result which returns several values.
*
* @param result The values to return, this may be {@code null}. {@link Number}s, {@link String}s, {@link Boolean}s,
* {@link Map}s, {@link ILuaObject}s, and {@code null} be converted to their corresponding lua type.
* All other types will be converted to nil.
* @return A result which will return these values when evaluated.
* @see #empty()
*/
@Nonnull
public static MethodResult of( Object... result )
{
return result == null ? empty() : new Immediate( result );
}
/**
* Wait for an event to occur on the computer, suspending the coroutine until it arises. This method is equivalent
* to {@code os.pullEvent()} in Lua.
*
* Normally you'll wish to consume the event using {@link #then(ILuaFunction)}. This can be done slightly more
* easily with {@link #pullEvent(ILuaFunction)}.
*
* If you want to listen to a specific event, it's easier to use {@link #pullEvent(String)} rather than
* running until the desired event is found.
*
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
* @see #pullEvent(ILuaFunction)
* @see #pullEvent(String)
*/
@Nonnull
public static MethodResult pullEvent()
{
return new OnEvent( false, null );
}
/**
* Wait for the specified event to occur on the computer, suspending the coroutine until it arises. This method is
* equivalent to {@code os.pullEvent(event)} in Lua.
*
* Normally you'll wish to consume the event using {@link #then(ILuaFunction)}. This can be done slightly more
* easily with {@link #pullEvent(String, ILuaFunction)}.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
* @see #pullEvent(String, ILuaFunction)
* @see #pullEvent()
*/
@Nonnull
public static MethodResult pullEvent( @Nonnull String filter )
{
Preconditions.checkNotNull( filter, "event cannot be null" );
return new OnEvent( false, filter );
}
/**
* Wait for an event to occur on the computer, suspending the coroutine until it arises. This method to
* {@link #pullEvent()} and {@link #then(ILuaFunction)}.
*
* If you want to listen to a specific event, it's easier to use {@link #pullEvent(String, ILuaFunction)} rather
* than running until the desired event is found.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent()
* @see #pullEvent(String, ILuaFunction)
*/
@Nonnull
public static MethodResult pullEvent( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( false, null ).then( callback );
}
/**
* Wait for the specified event to occur on the computer, suspending the coroutine until it arises. This method to
* {@link #pullEvent(String)} and {@link #then(ILuaFunction)}.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent(String)
* @see #pullEvent(ILuaFunction)
*/
@Nonnull
public static MethodResult pullEvent( @Nullable String filter, @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( false, filter ).then( callback );
}
/**
* The same as {@link #pullEvent()}, except {@code terminated} events are also passed to the callback, instead of
* throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
*/
@Nonnull
public static MethodResult pullEventRaw()
{
return new OnEvent( true, null );
}
/**
* The same as {@link #pullEvent(String)}, except {@code terminated} events are also passed to the callback, instead
* of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nonnull String filter )
{
return new OnEvent( true, filter );
}
/**
* The same as {@link #pullEvent(ILuaFunction)}, except {@code terminated} events are also passed to the callback,
* instead of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( true, null ).then( callback );
}
/**
* The same as {@link #pullEvent(String, ILuaFunction)}, except {@code terminated} events are also passed to the
* callback, instead of throwing an error. Only use this if you want to prevent program termination, which is not
* recommended.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nullable String filter, @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( true, filter ).then( callback );
}
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
* This should be used when you need to interact with the world in a thread-safe manner.
*
* @param callback The task to execute on the server thread.
* @return The constructed method result, which evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult onMainThread( @Nonnull ILuaCallable callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnMainThread( callback );
}
/**
* Consume the result of this {@link MethodResult} and return another result.
*
* Note this does NOT modify the current method result, rather returning a new (wrapped) one. You must return the
* result of this call if you wish to use it.
*
* @param callback The function which consumes the provided values.
* @return The constructed method result.
*/
@Nonnull
public final MethodResult then( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new AndThen( this, callback );
}
/**
* Execute a blocking task within a {@link ILuaContext} and return its result.
*
* @param consumer The task to execute with the provided Lua context.
* @return The constructed method result.
* @see #evaluate(ILuaContext)
* @deprecated This should not be used except to interface between the two call systems.
*/
@Deprecated
public static MethodResult withLuaContext( @Nonnull ILuaContextTask consumer )
{
Preconditions.checkNotNull( consumer, "consumer cannot be null" );
return new WithLuaContext( consumer );
}
/**
* Evaluate this result task using {@link ILuaContext} and return its result.
*
* @param context The context to execute with.
* @return The resulting values.
* @throws LuaException If an error was thrown while executing one of the methods within this future.
* @throws InterruptedException If the user shuts down or reboots the computer while the coroutine is suspended.
* @see #withLuaContext(ILuaContextTask)
* @deprecated This should not be used except to interface between the two call systems.
*/
@Deprecated
public final Object[] evaluate( @Nonnull ILuaContext context ) throws LuaException, InterruptedException
{
return LuaContextResultEvaluator.evaluate( context, this );
}
public static class Immediate extends MethodResult
{
@Nullable
private final Object[] values;
@Nullable
private Immediate( Object[] values )
{
this.values = values;
}
public Object[] getResult()
{
return values;
}
}
public static class OnEvent extends MethodResult
{
private final boolean raw;
private final String filter;
private OnEvent( boolean raw, String filter )
{
this.raw = raw;
this.filter = filter;
}
public boolean isRaw()
{
return raw;
}
@Nullable
public String getFilter()
{
return filter;
}
}
public static class OnMainThread extends MethodResult
{
private final ILuaCallable task;
public OnMainThread( ILuaCallable task )
{
this.task = task;
}
@Nonnull
public ILuaCallable getTask()
{
return task;
}
}
public static class AndThen extends MethodResult
{
private final MethodResult previous;
private final ILuaFunction callback;
private AndThen( MethodResult previous, ILuaFunction callback )
{
this.previous = previous;
this.callback = callback;
}
@Nonnull
public MethodResult getPrevious()
{
return previous;
}
@Nonnull
public ILuaFunction getCallback()
{
return callback;
}
}
public static class WithLuaContext extends MethodResult
{
private final ILuaContextTask consumer;
private WithLuaContext( ILuaContextTask consumer )
{
this.consumer = consumer;
}
@Nonnull
public ILuaContextTask getConsumer()
{
return consumer;
}
}
}

View File

@@ -1,13 +1,15 @@
/* /*
* This file is part of the public ComputerCraft API - http://www.computercraft.info * This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only. * Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info. * For help using the API, and posting your mods, visit the forums at computercraft.info.
*/ */
package dan200.computercraft.api.peripheral; package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -70,10 +72,46 @@ public interface IPeripheral
* InterruptedException will be thrown. This exception must not be caught or * InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @see #getMethodNames * @see #getMethodNames
* @deprecated Use {@link #callMethod(IComputerAccess, ICallContext, int, Object[])} instead.
*/ */
@Nullable @Nullable
@Deprecated
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException; Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe
* when interacting with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
* @param context The context of the current call.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.<br>
* Lua values of type "string" will be represented by Object type String.<br>
* Lua values of type "number" will be represented by Object type Double.<br>
* Lua values of type "boolean" will be represented by Object type Boolean.<br>
* Lua values of type "table" will be represented by Object type Map.<br>
* Lua values of any other type will be represented by a null object.<br>
* This array will be empty if no arguments are passed.
* @return The result of calling this method. Use {@link MethodResult#empty()} to return nothing or
* {@link MethodResult#of(Object...)} to return several values.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see #getMethodNames
*/
@Nonnull
@SuppressWarnings({ "deprecation" })
default MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( lua -> callMethod( computer, lua, method, arguments ) );
}
/** /**
* Is called when canAttachToSide has returned true, and a computer is attaching to the peripheral. * Is called when canAttachToSide has returned true, and a computer is attaching to the peripheral.
* *

View File

@@ -9,6 +9,7 @@ package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.inventory.IInventory; import net.minecraft.inventory.IInventory;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
@@ -238,10 +239,30 @@ public interface ITurtleAccess
* intercepted, or the computer will leak memory and end up in a broken state. * intercepted, or the computer will leak memory and end up in a broken state.
* @see ITurtleCommand * @see ITurtleCommand
* @see ILuaContext#pullEvent(String) * @see ILuaContext#pullEvent(String)
* @deprecated Use {@link #executeCommand(ITurtleCommand)} instead.
*/ */
@Nonnull @Nonnull
@Deprecated
Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException; Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
/**
* Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be
* executed on the main thread, so are guaranteed to be able to access Minecraft objects safely, and will be queued
* up with the turtles standard movement and tool commands.
*
* An issued command will return an unique integer, which will be supplied as a parameter to a "turtle_response"
* event issued to the turtle after the command has completed. Look at the Lua source code for "rom/apis/turtle" for
* how to build a Lua wrapper around this functionality.
*
* @param command An object which will execute the custom command when its point in the queue is reached
* @return The constructed method result. This evaluates to the result of the provided {@code command}.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
* @see ITurtleCommand
* @see MethodResult#pullEvent(String)
*/
@Nonnull
MethodResult executeCommand( @Nonnull ITurtleCommand command );
/** /**
* Start playing a specific animation. This will prevent other turtle commands from executing until * Start playing a specific animation. This will prevent other turtle commands from executing until
* it is finished. * it is finished.

View File

@@ -133,7 +133,7 @@ public interface ITurtleUpgrade
* @return The model that you wish to be used to render your upgrade, and a transformation to apply to it. Returning * @return The model that you wish to be used to render your upgrade, and a transformation to apply to it. Returning
* a transformation of {@code null} has the same effect as the identify matrix. * a transformation of {@code null} has the same effect as the identify matrix.
*/ */
@SideOnly(Side.CLIENT) @SideOnly( Side.CLIENT )
@Nonnull @Nonnull
Pair<IBakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side ); Pair<IBakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );

View File

@@ -8,7 +8,11 @@ package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*; import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.*; import dan200.computercraft.client.render.ItemPocketRenderer;
import dan200.computercraft.client.render.ItemPrintoutRenderer;
import dan200.computercraft.client.render.RenderOverlayCable;
import dan200.computercraft.client.render.TileEntityCableRenderer;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.shared.command.ContainerViewComputer; import dan200.computercraft.shared.command.ContainerViewComputer;
import dan200.computercraft.shared.computer.blocks.ComputerState; import dan200.computercraft.shared.computer.blocks.ComputerState;
import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.blocks.TileComputer;
@@ -31,7 +35,7 @@ import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera; import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import gnu.trove.map.hash.TIntIntHashMap;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiNewChat; import net.minecraft.client.gui.GuiNewChat;
@@ -71,7 +75,7 @@ import java.util.List;
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{ {
private static Int2IntOpenHashMap lastCounts = new Int2IntOpenHashMap(); private static TIntIntHashMap lastCounts = new TIntIntHashMap();
private long m_tick; private long m_tick;
private long m_renderFrame; private long m_renderFrame;

View File

@@ -18,14 +18,12 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.*; import net.minecraft.client.renderer.block.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.client.resource.IResourceType;
import net.minecraftforge.client.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.client.resource.VanillaResourceType;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -34,9 +32,8 @@ import javax.vecmath.Matrix4f;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
public class TurtleSmartItemModel implements IBakedModel, ISelectiveResourceReloadListener public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReloadListener
{ {
private static final Matrix4f s_identity, s_flip; private static final Matrix4f s_identity, s_flip;
@@ -158,9 +155,9 @@ public class TurtleSmartItemModel implements IBakedModel, ISelectiveResourceRelo
} }
@Override @Override
public void onResourceManagerReload( @Nonnull IResourceManager resourceManager, @Nonnull Predicate<IResourceType> resourcePredicate ) public void onResourceManagerReload( @Nonnull IResourceManager resourceManager )
{ {
if( resourcePredicate.test( VanillaResourceType.MODELS ) ) m_cachedModels.clear(); m_cachedModels.clear();
} }
private IBakedModel buildModel( TurtleModelCombination combo ) private IBakedModel buildModel( TurtleModelCombination combo )

View File

@@ -6,13 +6,12 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.terminal.TextBuffer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt; import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
@@ -41,27 +40,28 @@ public class BufferAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
case 0: case 0:
{ {
// len // len
return new Object[] { m_buffer.length() }; return MethodResult.of( m_buffer.length() );
} }
case 1: case 1:
{ {
// tostring // tostring
return new Object[] { m_buffer.toString() }; return MethodResult.of( m_buffer.toString() );
} }
case 2: case 2:
{ {
// read // read
int start = optInt( arguments, 0, 0 ); int start = optInt( arguments, 0, 0 );
int end = optInt( arguments, 1, m_buffer.length() ); int end = optInt( arguments, 1, m_buffer.length() );
return new Object[] { m_buffer.read( start, end ) }; return MethodResult.of( m_buffer.read( start, end ) );
} }
case 3: case 3:
{ {
@@ -70,7 +70,7 @@ public class BufferAPI implements ILuaAPI
int start = optInt( arguments, 1, 0 ); int start = optInt( arguments, 1, 0 );
int end = optInt( arguments, 2, start + text.length() ); int end = optInt( arguments, 2, start + text.length() );
m_buffer.write( text, start, end ); m_buffer.write( text, start, end );
return null; return MethodResult.empty();
} }
case 4: case 4:
{ {
@@ -79,14 +79,22 @@ public class BufferAPI implements ILuaAPI
int start = optInt( arguments, 1, 0 ); int start = optInt( arguments, 1, 0 );
int end = optInt( arguments, 2, m_buffer.length() ); int end = optInt( arguments, 2, m_buffer.length() );
m_buffer.fill( text, start, end ); m_buffer.fill( text, start, end );
return null; return MethodResult.empty();
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} }
public BufferAPI( IAPIEnvironment _env ) public BufferAPI( IAPIEnvironment _env )
@@ -110,8 +118,9 @@ public class BufferAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -124,12 +133,20 @@ public class BufferAPI implements ILuaAPI
throw ArgumentHelper.badArgument( 1, "positive number", Integer.toString( repetitions ) ); throw ArgumentHelper.badArgument( 1, "positive number", Integer.toString( repetitions ) );
} }
TextBuffer buffer = new TextBuffer( text, repetitions ); TextBuffer buffer = new TextBuffer( text, repetitions );
return new Object[] { new BufferLuaObject( buffer ) }; return MethodResult.of( new BufferLuaObject( buffer ) );
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} }

View File

@@ -6,26 +6,22 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.apis.handles.BinaryOutputHandle;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.apis.handles.BinaryWritableHandle; import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.BufferedReader; import javax.annotation.Nullable;
import java.io.BufferedWriter; import java.io.InputStream;
import java.nio.channels.ReadableByteChannel; import java.io.OutputStream;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
@@ -84,8 +80,9 @@ public class FSAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -100,7 +97,7 @@ public class FSAPI implements ILuaAPI
for(int i=0; i<results.length; ++i ) { for(int i=0; i<results.length; ++i ) {
table.put( i+1, results[i] ); table.put( i+1, results[i] );
} }
return new Object[] { table }; return MethodResult.of( table );
} }
catch( FileSystemException e ) catch( FileSystemException e )
{ {
@@ -112,13 +109,13 @@ public class FSAPI implements ILuaAPI
// combine // combine
String pathA = getString( args, 0 ); String pathA = getString( args, 0 );
String pathB = getString( args, 1 ); String pathB = getString( args, 1 );
return new Object[] { m_fileSystem.combine( pathA, pathB ) }; return MethodResult.of( m_fileSystem.combine( pathA, pathB ) );
} }
case 2: case 2:
{ {
// getName // getName
String path = getString( args, 0 ); String path = getString( args, 0 );
return new Object[]{ FileSystem.getName( path ) }; return MethodResult.of( FileSystem.getName( path ) );
} }
case 3: case 3:
{ {
@@ -126,7 +123,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 ); String path = getString( args, 0 );
try try
{ {
return new Object[]{ m_fileSystem.getSize( path ) }; return MethodResult.of( m_fileSystem.getSize( path ) );
} }
catch( FileSystemException e ) catch( FileSystemException e )
{ {
@@ -138,9 +135,9 @@ public class FSAPI implements ILuaAPI
// exists // exists
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
return new Object[]{ m_fileSystem.exists( path ) }; return MethodResult.of( m_fileSystem.exists( path ) );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
return new Object[]{ false }; return MethodResult.of( false );
} }
} }
case 5: case 5:
@@ -148,9 +145,9 @@ public class FSAPI implements ILuaAPI
// isDir // isDir
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
return new Object[]{ m_fileSystem.isDir( path ) }; return MethodResult.of( m_fileSystem.isDir( path ) );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
return new Object[]{ false }; return MethodResult.of( false );
} }
} }
case 6: case 6:
@@ -158,9 +155,9 @@ public class FSAPI implements ILuaAPI
// isReadOnly // isReadOnly
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
return new Object[]{ m_fileSystem.isReadOnly( path ) }; return MethodResult.of( m_fileSystem.isReadOnly( path ) );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
return new Object[]{ false }; return MethodResult.of( false );
} }
} }
case 7: case 7:
@@ -170,7 +167,7 @@ public class FSAPI implements ILuaAPI
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS ); m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path ); m_fileSystem.makeDir( path );
return null; return MethodResult.empty();
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -183,7 +180,7 @@ public class FSAPI implements ILuaAPI
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS ); m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest ); m_fileSystem.move( path, dest );
return null; return MethodResult.empty();
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -196,7 +193,7 @@ public class FSAPI implements ILuaAPI
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS ); m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest ); m_fileSystem.copy( path, dest );
return null; return MethodResult.empty();
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -208,7 +205,7 @@ public class FSAPI implements ILuaAPI
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS ); m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path ); m_fileSystem.delete( path );
return null; return MethodResult.empty();
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -225,44 +222,44 @@ public class FSAPI implements ILuaAPI
case "r": case "r":
{ {
// Open the file for reading, then create a wrapper around the reader // Open the file for reading, then create a wrapper around the reader
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 ); InputStream reader = m_fileSystem.openForRead( path );
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) }; return MethodResult.of( new EncodedInputHandle( reader ) );
} }
case "w": case "w":
{ {
// Open the file for writing, then create a wrapper around the writer // Open the file for writing, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 ); OutputStream writer = m_fileSystem.openForWrite( path, false );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) }; return MethodResult.of( new EncodedOutputHandle( writer ) );
} }
case "a": case "a":
{ {
// Open the file for appending, then create a wrapper around the writer // Open the file for appending, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 ); OutputStream writer = m_fileSystem.openForWrite( path, true );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) }; return MethodResult.of( new EncodedOutputHandle( writer ) );
} }
case "rb": case "rb":
{ {
// Open the file for binary reading, then create a wrapper around the reader // Open the file for binary reading, then create a wrapper around the reader
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() ); InputStream reader = m_fileSystem.openForRead( path );
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) }; return MethodResult.of( new BinaryInputHandle( reader ) );
} }
case "wb": case "wb":
{ {
// Open the file for binary writing, then create a wrapper around the writer // Open the file for binary writing, then create a wrapper around the writer
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() ); OutputStream writer = m_fileSystem.openForWrite( path, false );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) }; return MethodResult.of( new BinaryOutputHandle( writer ) );
} }
case "ab": case "ab":
{ {
// Open the file for binary appending, then create a wrapper around the reader // Open the file for binary appending, then create a wrapper around the reader
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() ); OutputStream writer = m_fileSystem.openForWrite( path, true );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) }; return MethodResult.of( new BinaryOutputHandle( writer ) );
} }
default: default:
throw new LuaException( "Unsupported mode" ); throw new LuaException( "Unsupported mode" );
} }
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
return new Object[] { null, e.getMessage() }; return MethodResult.of( null, e.getMessage() );
} }
} }
case 12: case 12:
@@ -272,9 +269,9 @@ public class FSAPI implements ILuaAPI
try { try {
if( !m_fileSystem.exists( path ) ) if( !m_fileSystem.exists( path ) )
{ {
return null; return MethodResult.empty();
} }
return new Object[]{ m_fileSystem.getMountLabel( path ) }; return MethodResult.of( m_fileSystem.getMountLabel( path ) );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -287,9 +284,9 @@ public class FSAPI implements ILuaAPI
long freeSpace = m_fileSystem.getFreeSpace( path ); long freeSpace = m_fileSystem.getFreeSpace( path );
if( freeSpace >= 0 ) if( freeSpace >= 0 )
{ {
return new Object[]{ freeSpace }; return MethodResult.of( freeSpace );
} }
return new Object[]{ "unlimited" }; return MethodResult.of( "unlimited" );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -305,7 +302,7 @@ public class FSAPI implements ILuaAPI
for(int i=0; i<results.length; ++i ) { for(int i=0; i<results.length; ++i ) {
table.put( i+1, results[i] ); table.put( i+1, results[i] );
} }
return new Object[] { table }; return MethodResult.of( table );
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
throw new LuaException( e.getMessage() ); throw new LuaException( e.getMessage() );
} }
@@ -314,13 +311,21 @@ public class FSAPI implements ILuaAPI
{ {
// getDir // getDir
String path = getString( args, 0 ); String path = getString( args, 0 );
return new Object[]{ FileSystem.getDirectory( path ) }; return MethodResult.of( FileSystem.getDirectory( path ) );
} }
default: default:
{ {
assert( false ); assert( false );
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} }

View File

@@ -8,12 +8,12 @@ package dan200.computercraft.core.apis;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.*; import dan200.computercraft.core.apis.http.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@@ -102,8 +102,9 @@ public class HTTPAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -164,11 +165,11 @@ public class HTTPAPI implements ILuaAPI
{ {
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) ); m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
} }
return new Object[]{ true }; return MethodResult.of(true);
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[]{ false, e.getMessage() }; return MethodResult.of( false, e.getMessage() );
} }
} }
case 1: case 1:
@@ -186,11 +187,11 @@ public class HTTPAPI implements ILuaAPI
{ {
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) ); m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
} }
return new Object[]{ true }; return MethodResult.of( true );
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[]{ false, e.getMessage() }; return MethodResult.of( false, e.getMessage() );
} }
} }
case 2: // websocket case 2: // websocket
@@ -223,20 +224,28 @@ public class HTTPAPI implements ILuaAPI
{ {
m_httpTasks.add( connector ); m_httpTasks.add( connector );
} }
return new Object[]{ true }; return MethodResult.of(true);
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[]{ false, e.getMessage() }; return MethodResult.of( false, e.getMessage() );
} }
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
public void addCloseable( Closeable closeable ) public void addCloseable( Closeable closeable )
{ {
synchronized( m_closeables ) synchronized( m_closeables )

View File

@@ -6,12 +6,12 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil; import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*; import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -221,7 +221,8 @@ public class OSAPI implements ILuaAPI
} }
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException @Nonnull
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -229,7 +230,7 @@ public class OSAPI implements ILuaAPI
{ {
// queueEvent // queueEvent
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) ); queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
return null; return MethodResult.empty();
} }
case 1: case 1:
{ {
@@ -238,7 +239,7 @@ public class OSAPI implements ILuaAPI
synchronized( m_timers ) synchronized( m_timers )
{ {
m_timers.put( m_nextTimerToken, new Timer( (int)Math.round( timer / 0.05 ) ) ); m_timers.put( m_nextTimerToken, new Timer( (int)Math.round( timer / 0.05 ) ) );
return new Object[] { m_nextTimerToken++ }; return MethodResult.of( m_nextTimerToken++ );
} }
} }
case 2: case 2:
@@ -253,33 +254,33 @@ public class OSAPI implements ILuaAPI
{ {
int day = (time > m_time) ? m_day : (m_day + 1); int day = (time > m_time) ? m_day : (m_day + 1);
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) ); m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return new Object[] { m_nextAlarmToken++ }; return MethodResult.of( m_nextAlarmToken++ );
} }
} }
case 3: case 3:
{ {
// shutdown // shutdown
m_apiEnvironment.shutdown(); m_apiEnvironment.shutdown();
return null; return MethodResult.empty();
} }
case 4: case 4:
{ {
// reboot // reboot
m_apiEnvironment.reboot(); m_apiEnvironment.reboot();
return null; return MethodResult.empty();
} }
case 5: case 5:
case 6: case 6:
{ {
// computerID/getComputerID // computerID/getComputerID
return new Object[] { getComputerID() }; return MethodResult.of( getComputerID() );
} }
case 7: case 7:
{ {
// setComputerLabel // setComputerLabel
String label = optString( args, 0, null ); String label = optString( args, 0, null );
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) ); m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
return null; return MethodResult.empty();
} }
case 8: case 8:
case 9: case 9:
@@ -288,16 +289,16 @@ public class OSAPI implements ILuaAPI
String label = m_apiEnvironment.getLabel(); String label = m_apiEnvironment.getLabel();
if( label != null ) if( label != null )
{ {
return new Object[] { label }; return MethodResult.of( label );
} }
return null; return MethodResult.empty();
} }
case 10: case 10:
{ {
// clock // clock
synchronized( m_timers ) synchronized( m_timers )
{ {
return new Object[] { m_clock * 0.05 }; return MethodResult.of( m_clock * 0.05 );
} }
} }
case 11: case 11:
@@ -310,19 +311,19 @@ public class OSAPI implements ILuaAPI
{ {
// Get Hour of day (UTC) // Get Hour of day (UTC)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getTimeForCalendar( c ) }; return MethodResult.of( getTimeForCalendar( c ) );
} }
case "local": case "local":
{ {
// Get Hour of day (local time) // Get Hour of day (local time)
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
return new Object[] { getTimeForCalendar( c ) }; return MethodResult.of( getTimeForCalendar( c ) );
} }
case "ingame": case "ingame":
// Get ingame hour // Get ingame hour
synchronized( m_alarms ) synchronized( m_alarms )
{ {
return new Object[] { m_time }; return MethodResult.of( m_time );
} }
default: default:
throw new LuaException( "Unsupported operation" ); throw new LuaException( "Unsupported operation" );
@@ -338,19 +339,19 @@ public class OSAPI implements ILuaAPI
{ {
// Get numbers of days since 1970-01-01 (utc) // Get numbers of days since 1970-01-01 (utc)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getDayForCalendar( c ) }; return MethodResult.of( getDayForCalendar( c ) );
} }
case "local": case "local":
{ {
// Get numbers of days since 1970-01-01 (local time) // Get numbers of days since 1970-01-01 (local time)
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
return new Object[] { getDayForCalendar( c ) }; return MethodResult.of( getDayForCalendar( c ) );
} }
case "ingame": case "ingame":
// Get game day // Get game day
synchronized( m_alarms ) synchronized( m_alarms )
{ {
return new Object[] { m_day }; return MethodResult.of( m_day );
} }
default: default:
throw new LuaException( "Unsupported operation" ); throw new LuaException( "Unsupported operation" );
@@ -367,7 +368,7 @@ public class OSAPI implements ILuaAPI
m_timers.remove( token ); m_timers.remove( token );
} }
} }
return null; return MethodResult.empty();
} }
case 14: case 14:
{ {
@@ -380,7 +381,7 @@ public class OSAPI implements ILuaAPI
m_alarms.remove( token ); m_alarms.remove( token );
} }
} }
return null; return MethodResult.empty();
} }
case 15: case 15:
{ {
@@ -392,21 +393,21 @@ public class OSAPI implements ILuaAPI
{ {
// Get utc epoch // Get utc epoch
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getEpochForCalendar( c ) }; return MethodResult.of( getEpochForCalendar( c ) );
} }
case "local": case "local":
{ {
// Get local epoch // Get local epoch
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
return new Object[] { getEpochForCalendar( c ) }; return MethodResult.of( getEpochForCalendar( c ) );
} }
case "ingame": case "ingame":
// Get in-game epoch // Get in-game epoch
synchronized( m_alarms ) synchronized( m_alarms )
{ {
return new Object[] { return MethodResult.of(
m_day * 86400000 + (int) (m_time * 3600000.0f) m_day * 86400000 + (int) (m_time * 3600000.0f)
}; );
} }
default: default:
throw new LuaException( "Unsupported operation" ); throw new LuaException( "Unsupported operation" );
@@ -414,11 +415,18 @@ public class OSAPI implements ILuaAPI
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
// Private methods // Private methods
private void queueLuaEvent( String event, Object[] args ) private void queueLuaEvent( String event, Object[] args )

View File

@@ -8,9 +8,8 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread; import dan200.computercraft.core.computer.ComputerThread;
@@ -98,7 +97,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
m_attached = false; m_attached = false;
} }
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException public MethodResult call( ICallContext context, String methodName, Object[] arguments ) throws LuaException
{ {
int method = -1; int method = -1;
synchronized( this ) synchronized( this )
@@ -368,8 +367,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -389,7 +389,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
} }
return new Object[] { present }; return MethodResult.of( present );
} }
case 1: case 1:
{ {
@@ -408,10 +408,10 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
if( type != null ) if( type != null )
{ {
return new Object[] { type }; return MethodResult.of( type );
} }
} }
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
@@ -435,9 +435,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
for(int i=0; i<methods.length; ++i ) { for(int i=0; i<methods.length; ++i ) {
table.put( i+1, methods[i] ); table.put( i+1, methods[i] );
} }
return new Object[] { table }; return MethodResult.of( table );
} }
return null; return MethodResult.empty();
} }
case 3: case 3:
{ {
@@ -462,11 +462,19 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
// Privates // Privates
private int parseSide( Object[] args ) throws LuaException private int parseSide( Object[] args ) throws LuaException

View File

@@ -6,12 +6,12 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -56,8 +56,9 @@ public class RedstoneAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -69,7 +70,7 @@ public class RedstoneAPI implements ILuaAPI
{ {
table.put( i+1, Computer.s_sideNames[i] ); table.put( i+1, Computer.s_sideNames[i] );
} }
return new Object[] { table }; return MethodResult.of( table );
} }
case 1: case 1:
{ {
@@ -77,19 +78,19 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args ); int side = parseSide( args );
boolean output = getBoolean( args, 1 ); boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 ); m_environment.setOutput( side, output ? 15 : 0 );
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
// getOutput // getOutput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) > 0 }; return MethodResult.of( m_environment.getOutput( side ) > 0 );
} }
case 3: case 3:
{ {
// getInput // getInput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) > 0 }; return MethodResult.of( m_environment.getInput( side ) > 0 );
} }
case 4: case 4:
{ {
@@ -97,19 +98,19 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args ); int side = parseSide( args );
int output = getInt( args, 1 ); int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output ); m_environment.setBundledOutput( side, output );
return null; return MethodResult.empty();
} }
case 5: case 5:
{ {
// getBundledOutput // getBundledOutput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getBundledOutput( side ) }; return MethodResult.of( m_environment.getBundledOutput( side ) );
} }
case 6: case 6:
{ {
// getBundledInput // getBundledInput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getBundledInput( side ) }; return MethodResult.of( m_environment.getBundledInput( side ) );
} }
case 7: case 7:
{ {
@@ -117,7 +118,7 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args ); int side = parseSide( args );
int mask = getInt( args, 1 ); int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side ); int input = m_environment.getBundledInput( side );
return new Object[] { ((input & mask) == mask) }; return MethodResult.of( ((input & mask) == mask) );
} }
case 8: case 8:
case 9: case 9:
@@ -130,28 +131,36 @@ public class RedstoneAPI implements ILuaAPI
throw new LuaException( "Expected number in range 0-15" ); throw new LuaException( "Expected number in range 0-15" );
} }
m_environment.setOutput( side, output ); m_environment.setOutput( side, output );
return null; return MethodResult.empty();
} }
case 10: case 10:
case 11: case 11:
{ {
// getAnalogOutput/getAnalogueOutput // getAnalogOutput/getAnalogueOutput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) }; return MethodResult.of( m_environment.getOutput( side ) );
} }
case 12: case 12:
case 13: case 13:
{ {
// getAnalogInput/getAnalogueInput // getAnalogInput/getAnalogueInput
int side = parseSide( args ); int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) }; return MethodResult.of( m_environment.getInput( side ) );
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
private int parseSide( Object[] args ) throws LuaException private int parseSide( Object[] args ) throws LuaException
{ {

View File

@@ -6,15 +6,15 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.IComputerEnvironment; import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Palette; import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.*; import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -64,8 +64,7 @@ public class TermAPI implements ILuaAPI
"setPaletteColour", "setPaletteColour",
"setPaletteColor", "setPaletteColor",
"getPaletteColour", "getPaletteColour",
"getPaletteColor", "getPaletteColor"
"getCursorBlink",
}; };
} }
@@ -84,11 +83,9 @@ public class TermAPI implements ILuaAPI
return colour; return colour;
} }
public static Object[] encodeColour( int colour ) throws LuaException public static MethodResult encodeColour( int colour )
{ {
return new Object[] { return MethodResult.of( 1 << colour );
1 << colour
};
} }
public static void setColour( Terminal terminal, int colour, double r, double g, double b ) public static void setColour( Terminal terminal, int colour, double r, double g, double b )
@@ -100,8 +97,9 @@ public class TermAPI implements ILuaAPI
} }
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -120,7 +118,7 @@ public class TermAPI implements ILuaAPI
m_terminal.write( text ); m_terminal.write( text );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() ); m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
} }
return null; return MethodResult.empty();
} }
case 1: case 1:
{ {
@@ -130,7 +128,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.scroll(y); m_terminal.scroll(y);
} }
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
@@ -141,7 +139,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.setCursorPos( x, y ); m_terminal.setCursorPos( x, y );
} }
return null; return MethodResult.empty();
} }
case 3: case 3:
{ {
@@ -151,7 +149,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.setCursorBlink( b ); m_terminal.setCursorBlink( b );
} }
return null; return MethodResult.empty();
} }
case 4: case 4:
{ {
@@ -162,7 +160,7 @@ public class TermAPI implements ILuaAPI
x = m_terminal.getCursorX(); x = m_terminal.getCursorX();
y = m_terminal.getCursorY(); y = m_terminal.getCursorY();
} }
return new Object[] { x + 1, y + 1 }; return MethodResult.of( x + 1, y + 1 );
} }
case 5: case 5:
{ {
@@ -173,7 +171,7 @@ public class TermAPI implements ILuaAPI
width = m_terminal.getWidth(); width = m_terminal.getWidth();
height = m_terminal.getHeight(); height = m_terminal.getHeight();
} }
return new Object[] { width, height }; return MethodResult.of( width, height );
} }
case 6: case 6:
{ {
@@ -182,7 +180,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.clear(); m_terminal.clear();
} }
return null; return MethodResult.empty();
} }
case 7: case 7:
{ {
@@ -191,7 +189,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.clearLine(); m_terminal.clearLine();
} }
return null; return MethodResult.empty();
} }
case 8: case 8:
case 9: case 9:
@@ -202,7 +200,7 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.setTextColour( colour ); m_terminal.setTextColour( colour );
} }
return null; return MethodResult.empty();
} }
case 10: case 10:
case 11: case 11:
@@ -213,13 +211,13 @@ public class TermAPI implements ILuaAPI
{ {
m_terminal.setBackgroundColour( colour ); m_terminal.setBackgroundColour( colour );
} }
return null; return MethodResult.empty();
} }
case 12: case 12:
case 13: case 13:
{ {
// isColour/isColor // isColour/isColor
return new Object[] { m_environment.isColour() }; return MethodResult.of( m_environment.isColour() );
} }
case 14: case 14:
case 15: case 15:
@@ -249,7 +247,7 @@ public class TermAPI implements ILuaAPI
m_terminal.blit( text, textColour, backgroundColour ); m_terminal.blit( text, textColour, backgroundColour );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() ); m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
} }
return null; return MethodResult.empty();
} }
case 19: case 19:
case 20: case 20:
@@ -269,7 +267,7 @@ public class TermAPI implements ILuaAPI
double b = getReal( args, 3 ); double b = getReal( args, 3 );
setColour( m_terminal, colour, r, g, b ); setColour( m_terminal, colour, r, g, b );
} }
return null; return MethodResult.empty();
} }
case 21: case 21:
case 22: case 22:
@@ -280,21 +278,26 @@ public class TermAPI implements ILuaAPI
{ {
if ( m_terminal.getPalette() != null ) if ( m_terminal.getPalette() != null )
{ {
return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) ); return MethodResult.of( (Object[]) ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) ) );
} }
} }
return null; return MethodResult.empty();
} }
case 23:
// getCursorBlink
return new Object[] { m_terminal.getCursorBlink() };
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
private static int getHighestBit( int group ) private static int getHighestBit( int group )
{ {
int bit = 0; int bit = 0;

View File

@@ -1,91 +0,0 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
/**
* A seekable, readable byte channel which is backed by a simple byte array.
*/
public class ArrayByteChannel implements SeekableByteChannel
{
private boolean closed = false;
private int position = 0;
private final byte[] backing;
public ArrayByteChannel( byte[] backing )
{
this.backing = backing;
}
@Override
public int read( ByteBuffer destination ) throws IOException
{
if( closed ) throw new ClosedChannelException();
Preconditions.checkNotNull( destination, "destination" );
if( position >= backing.length ) return -1;
int remaining = Math.min( backing.length - position, destination.remaining() );
destination.put( backing, position, remaining );
position += remaining;
return remaining;
}
@Override
public int write( ByteBuffer src ) throws IOException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public long position() throws IOException
{
if( closed ) throw new ClosedChannelException();
return position;
}
@Override
public SeekableByteChannel position( long newPosition ) throws IOException
{
if( closed ) throw new ClosedChannelException();
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
{
throw new IllegalArgumentException( "Position out of bounds" );
}
position = (int) newPosition;
return this;
}
@Override
public long size() throws IOException
{
if( closed ) throw new ClosedChannelException();
return backing.length;
}
@Override
public SeekableByteChannel truncate( long size ) throws IOException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public boolean isOpen()
{
return !closed;
}
@Override
public void close()
{
closed = true;
}
}

View File

@@ -0,0 +1,124 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.io.ByteStreams;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
public class BinaryInputHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final InputStream m_stream;
public BinaryInputHandle( InputStream reader )
{
super( reader );
this.m_stream = reader;
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"read",
"readAll",
"close",
};
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
// read
checkOpen();
try
{
if( args.length > 0 && args[ 0 ] != null )
{
int count = getInt( args, 0 );
if( count < 0 )
{
// Whilst this may seem absurd to allow reading 0 bytes, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
byte[] bytes = new byte[ count ];
int read = m_stream.read( bytes );
if( read < 0 ) return MethodResult.empty();
if( read < count ) bytes = Arrays.copyOf( bytes, read );
return MethodResult.of( bytes );
}
else
{
byte[] buffer = new byte[ BUFFER_SIZE ];
// Read the initial set of bytes, failing if none are read.
int read = m_stream.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
ByteArrayOutputStream out = new ByteArrayOutputStream( read );
count -= read;
out.write( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= buffer.length && count > 0 )
{
read = m_stream.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.write( buffer, 0, read );
}
return MethodResult.of( new Object[]{ out.toByteArray() } );
}
}
else
{
int b = m_stream.read();
return b == -1 ? MethodResult.empty() : MethodResult.of( b );
}
}
catch( IOException e )
{
return MethodResult.empty();
}
case 1:
// readAll
checkOpen();
try
{
byte[] out = ByteStreams.toByteArray( m_stream );
return out == null ? MethodResult.empty() : MethodResult.of( out );
}
catch( IOException e )
{
return MethodResult.empty();
}
case 2:
//close
close();
return MethodResult.empty();
default:
return MethodResult.empty();
}
}
}

View File

@@ -0,0 +1,85 @@
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.apis.ArgumentHelper;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
public class BinaryOutputHandle extends HandleGeneric
{
private final OutputStream m_writer;
public BinaryOutputHandle( OutputStream writer )
{
super( writer );
this.m_writer = writer;
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"write",
"flush",
"close",
};
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
// write
checkOpen();
try
{
if( args.length > 0 && args[ 0 ] instanceof Number )
{
int number = ((Number) args[ 0 ]).intValue();
m_writer.write( number );
}
else if( args.length > 0 && args[ 0 ] instanceof String )
{
String value = (String) args[ 0 ];
m_writer.write( StringUtil.encodeString( value ) );
}
else
{
throw ArgumentHelper.badArgument( 0, "string or number", args.length > 0 ? args[ 0 ] : null );
}
return MethodResult.empty();
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
case 1:
// flush
checkOpen();
try
{
m_writer.flush();
return MethodResult.empty();
}
catch( IOException e )
{
return MethodResult.empty();
}
case 2:
//close
close();
return MethodResult.empty();
default:
return MethodResult.empty();
}
}
}

View File

@@ -1,205 +0,0 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
public class BinaryReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final ReadableByteChannel m_reader;
private final SeekableByteChannel m_seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
{
super( closeable );
this.m_reader = channel;
this.m_seekable = asSeekable( channel );
}
public BinaryReadableHandle( ReadableByteChannel channel )
{
this( channel, channel );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
// read
checkOpen();
try
{
if( args.length > 0 && args[ 0 ] != null )
{
int count = getInt( args, 0 );
if( count < 0 )
{
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count == 0 && m_seekable != null )
{
return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
}
if( count <= BUFFER_SIZE )
{
ByteBuffer buffer = ByteBuffer.allocate( count );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
}
else
{
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
int totalRead = read;
// If we failed to read "enough" here, let's just abort
if( totalRead >= count || read < BUFFER_SIZE )
{
return new Object[] { Arrays.copyOf( buffer.array(), read ) };
}
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
// than doubling up the buffer each time.
List<ByteBuffer> parts = new ArrayList<>( 4 );
parts.add( buffer );
while( totalRead < count && read >= BUFFER_SIZE )
{
buffer = ByteBuffer.allocate( BUFFER_SIZE );
totalRead += read = m_reader.read( buffer );
parts.add( buffer );
}
// Now just copy all the bytes across!
byte[] bytes = new byte[ totalRead ];
int pos = 0;
for( ByteBuffer part : parts )
{
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
pos += part.position();
}
return new Object[] { bytes };
}
}
else
{
single.clear();
int b = m_reader.read( single );
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
}
}
catch( IOException e )
{
return null;
}
case 1:
// readAll
checkOpen();
try
{
int expected = 32;
if( m_seekable != null )
{
expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
}
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
ByteBuffer buf = ByteBuffer.allocate( 8192 );
boolean readAnything = false;
while( true )
{
buf.clear();
int r = m_reader.read( buf );
if( r == -1 ) break;
readAnything = true;
stream.write( buf.array(), 0, r );
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
case 2:
{
// readLine
checkOpen();
boolean withTrailing = optBoolean( args, 0, false );
try
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false;
while( true )
{
single.clear();
int r = m_reader.read( single );
if( r == -1 ) break;
readAnything = true;
byte b = single.get( 0 );
if( b == '\n' )
{
if( withTrailing ) stream.write( b );
break;
}
else
{
stream.write( b );
}
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
}
case 3:
//close
close();
return null;
case 4:
// seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
}
}
}

View File

@@ -1,105 +0,0 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ArgumentHelper;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
public class BinaryWritableHandle extends HandleGeneric
{
private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final WritableByteChannel m_writer;
private final SeekableByteChannel m_seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
{
super( closeable );
this.m_writer = channel;
this.m_seekable = asSeekable( channel );
}
public BinaryWritableHandle( WritableByteChannel channel )
{
this( channel, channel );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
// write
checkOpen();
try
{
if( args.length > 0 && args[ 0 ] instanceof Number )
{
int number = ((Number) args[ 0 ]).intValue();
single.clear();
single.put( (byte) number );
single.flip();
m_writer.write( single );
}
else if( args.length > 0 && args[ 0 ] instanceof String )
{
String value = (String) args[ 0 ];
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
}
else
{
throw ArgumentHelper.badArgument( 0, "string or number", args.length > 0 ? args[ 0 ] : null );
}
return null;
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
case 1:
// flush
checkOpen();
try
{
// Technically this is not needed
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
return null;
}
catch( IOException e )
{
return null;
}
case 2:
//close
close();
return null;
case 3:
// seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
}
}
}

View File

@@ -1,43 +1,55 @@
package dan200.computercraft.core.apis.handles; package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.BufferedReader; import java.io.*;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt; import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class EncodedReadableHandle extends HandleGeneric public class EncodedInputHandle extends HandleGeneric
{ {
private static final int BUFFER_SIZE = 8192; private static final int BUFFER_SIZE = 8192;
private BufferedReader m_reader; private final BufferedReader m_reader;
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable ) public EncodedInputHandle( BufferedReader reader )
{ {
super( closable ); super( reader );
this.m_reader = reader; this.m_reader = reader;
} }
public EncodedReadableHandle( @Nonnull BufferedReader reader ) public EncodedInputHandle( InputStream stream )
{ {
this( reader, reader ); this( stream, "UTF-8" );
}
public EncodedInputHandle( InputStream stream, String encoding )
{
this( makeReader( stream, encoding ) );
}
private static BufferedReader makeReader( InputStream stream, String encoding )
{
if( encoding == null ) encoding = "UTF-8";
InputStreamReader streamReader;
try
{
streamReader = new InputStreamReader( stream, encoding );
}
catch( UnsupportedEncodingException e )
{
streamReader = new InputStreamReader( stream );
}
return new BufferedReader( streamReader );
} }
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()
{ {
return new String[] { return new String[] {
"readLine", "readLine",
"readAll", "readAll",
@@ -46,35 +58,31 @@ public class EncodedReadableHandle extends HandleGeneric
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
case 0: case 0:
{
// readLine // readLine
checkOpen(); checkOpen();
boolean withTrailing = optBoolean( args, 0, false );
try try
{ {
String line = m_reader.readLine(); String line = m_reader.readLine();
if( line != null ) if( line != null )
{ {
// While this is technically inaccurate, it's better than nothing return MethodResult.of( line );
if( withTrailing ) line += "\n";
return new Object[] { line };
} }
else else
{ {
return null; return MethodResult.empty();
} }
} }
catch( IOException e ) catch( IOException e )
{ {
return null; return MethodResult.empty();
} }
}
case 1: case 1:
// readAll // readAll
checkOpen(); checkOpen();
@@ -91,17 +99,18 @@ public class EncodedReadableHandle extends HandleGeneric
result.append( "\n" ); result.append( "\n" );
} }
} }
return new Object[] { result.toString() }; return MethodResult.of( result.toString() );
} }
catch( IOException e ) catch( IOException e )
{ {
return null; return MethodResult.empty();
} }
case 2: case 2:
// close // close
close(); close();
return null; return MethodResult.empty();
case 3: case 3:
// read
checkOpen(); checkOpen();
try try
{ {
@@ -118,7 +127,7 @@ public class EncodedReadableHandle extends HandleGeneric
char[] chars = new char[ count ]; char[] chars = new char[ count ];
int read = m_reader.read( chars ); int read = m_reader.read( chars );
return read < 0 ? null : new Object[] { new String( chars, 0, read ) }; return read < 0 ? MethodResult.empty() : MethodResult.of( new String( chars, 0, read ) );
} }
else else
{ {
@@ -143,30 +152,15 @@ public class EncodedReadableHandle extends HandleGeneric
out.append( buffer, 0, read ); out.append( buffer, 0, read );
} }
return new Object[] { out.toString() }; return MethodResult.of( out.toString() );
} }
} }
catch( IOException e ) catch( IOException e )
{ {
return null; return MethodResult.empty();
} }
default: default:
return null; return MethodResult.empty();
} }
} }
public static BufferedReader openUtf8( ReadableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedReader open( ReadableByteChannel channel, Charset charset )
{
// Create a charset decoder with the same properties as StreamDecoder does for
// InputStreams: namely, replace everything instead of erroring.
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput( CodingErrorAction.REPLACE )
.onUnmappableCharacter( CodingErrorAction.REPLACE );
return new BufferedReader( Channels.newReader( channel, decoder, -1 ) );
}
} }

View File

@@ -1,32 +1,45 @@
package dan200.computercraft.core.apis.handles; package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.BufferedWriter; import java.io.*;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
public class EncodedWritableHandle extends HandleGeneric public class EncodedOutputHandle extends HandleGeneric
{ {
private BufferedWriter m_writer; private final BufferedWriter m_writer;
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable ) public EncodedOutputHandle( BufferedWriter writer )
{ {
super( closable ); super( writer );
this.m_writer = writer; this.m_writer = writer;
} }
public EncodedWritableHandle( @Nonnull BufferedWriter writer ) public EncodedOutputHandle( OutputStream stream )
{ {
this( writer, writer ); this( stream, "UTF-8" );
}
public EncodedOutputHandle( OutputStream stream, String encoding )
{
this( makeWriter( stream, encoding ) );
}
private static BufferedWriter makeWriter( OutputStream stream, String encoding )
{
if( encoding == null ) encoding = "UTF-8";
OutputStreamWriter streamWriter;
try
{
streamWriter = new OutputStreamWriter( stream, encoding );
}
catch( UnsupportedEncodingException e )
{
streamWriter = new OutputStreamWriter( stream );
}
return new BufferedWriter( streamWriter );
} }
@Nonnull @Nonnull
@@ -41,8 +54,9 @@ public class EncodedWritableHandle extends HandleGeneric
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -62,7 +76,7 @@ public class EncodedWritableHandle extends HandleGeneric
try try
{ {
m_writer.write( text, 0, text.length() ); m_writer.write( text, 0, text.length() );
return null; return MethodResult.empty();
} }
catch( IOException e ) catch( IOException e )
{ {
@@ -86,7 +100,7 @@ public class EncodedWritableHandle extends HandleGeneric
{ {
m_writer.write( text, 0, text.length() ); m_writer.write( text, 0, text.length() );
m_writer.newLine(); m_writer.newLine();
return null; return MethodResult.empty();
} }
catch( IOException e ) catch( IOException e )
{ {
@@ -99,34 +113,18 @@ public class EncodedWritableHandle extends HandleGeneric
try try
{ {
m_writer.flush(); m_writer.flush();
return null; return MethodResult.empty();
} }
catch( IOException e ) catch( IOException e )
{ {
return null; return MethodResult.empty();
} }
case 3: case 3:
// close // close
close(); close();
return null; return MethodResult.empty();
default: default:
return null; return MethodResult.empty();
} }
} }
public static BufferedWriter openUtf8( WritableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedWriter open( WritableByteChannel channel, Charset charset )
{
// Create a charset encoder with the same properties as StreamEncoder does for
// OutputStreams: namely, replace everything instead of erroring.
CharsetEncoder encoder = charset.newEncoder()
.onMalformedInput( CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return new BufferedWriter( Channels.newWriter( channel, encoder, -1 ) );
}
} }

View File

@@ -1,25 +1,23 @@
package dan200.computercraft.core.apis.handles; package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.SeekableByteChannel;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
public abstract class HandleGeneric implements ILuaObject public abstract class HandleGeneric implements ILuaObject
{ {
private Closeable m_closable; protected final Closeable m_closable;
private boolean m_open = true; protected boolean m_open = true;
protected HandleGeneric( @Nonnull Closeable closable ) public HandleGeneric( Closeable m_closable )
{ {
this.m_closable = closable; this.m_closable = m_closable;
} }
protected void checkOpen() throws LuaException protected void checkOpen() throws LuaException
@@ -27,7 +25,7 @@ public abstract class HandleGeneric implements ILuaObject
if( !m_open ) throw new LuaException( "attempt to use a closed file" ); if( !m_open ) throw new LuaException( "attempt to use a closed file" );
} }
protected final void close() protected void close()
{ {
try try
{ {
@@ -37,64 +35,13 @@ public abstract class HandleGeneric implements ILuaObject
catch( IOException ignored ) catch( IOException ignored )
{ {
} }
m_closable = null;
} }
/** @Nullable
* Shared implementation for various file handle types @Override
* @Deprecated
* @param channel The channel to seek in public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
* @param args The Lua arguments to process, like Lua's {@code file:seek}.
* @return The new position of the file, or null if some error occured.
* @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/
protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException
{ {
try return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
{
String whence = optString( args, 0, "cur" );
long offset = optInt( args, 1, 0 );
switch( whence )
{
case "set":
channel.position( offset );
break;
case "cur":
channel.position( channel.position() + offset );
break;
case "end":
channel.position( channel.size() + offset );
break;
default:
throw new LuaException( "bad argument #1 to 'seek' (invalid option '" + whence + "'" );
}
return new Object[]{ channel.position() };
}
catch( IllegalArgumentException e )
{
return new Object[]{ false, "Position is negative" };
}
catch( IOException e )
{
return null;
}
}
protected static SeekableByteChannel asSeekable( Channel channel )
{
if( !(channel instanceof SeekableByteChannel) ) return null;
SeekableByteChannel seekable = (SeekableByteChannel) channel;
try
{
seekable.position( seekable.position() );
return seekable;
}
catch( IOException | UnsupportedOperationException e )
{
return null;
}
} }
} }

View File

@@ -8,7 +8,7 @@ package dan200.computercraft.core.apis.http;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import dan200.computercraft.shared.util.ThreadUtils; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
@@ -24,14 +24,18 @@ public final class HTTPExecutor
public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor( public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor(
4, Integer.MAX_VALUE, 4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS, 60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), new SynchronousQueue<Runnable>(),
ThreadUtils.builder( "HTTP" ) new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-HTTP-%d" )
.build() .build()
) ); ) );
public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, ThreadUtils.builder( "Netty" ) public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-Netty-%d" )
.build() .build()
); );

View File

@@ -9,21 +9,16 @@ package dan200.computercraft.core.apis.http;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.handles.ArrayByteChannel; import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -186,10 +181,6 @@ public class HTTPRequest implements Runnable
byte[] result = ByteStreams.toByteArray( is ); byte[] result = ByteStreams.toByteArray( is );
is.close(); is.close();
String encoding = connection.getContentEncoding();
Charset charset = encoding != null && Charset.isSupported( encoding )
? Charset.forName( encoding ) : StandardCharsets.UTF_8;
// We've got some sort of response, so let's build a resulting object. // We've got some sort of response, so let's build a resulting object.
Joiner joiner = Joiner.on( ',' ); Joiner joiner = Joiner.on( ',' );
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
@@ -201,11 +192,9 @@ public class HTTPRequest implements Runnable
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD, m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
getHeaderSize( connection.getHeaderFields() ) + result.length ); getHeaderSize( connection.getHeaderFields() ) + result.length );
SeekableByteChannel contents = new ArrayByteChannel( result ); InputStream contents = new ByteArrayInputStream( result );
ILuaObject stream = wrapStream( ILuaObject stream = wrapStream(
m_binary m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
? new BinaryReadableHandle( contents )
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, charset ) ),
connection.getResponseCode(), headers connection.getResponseCode(), headers
); );
@@ -246,8 +235,9 @@ public class HTTPRequest implements Runnable
return newMethods; return newMethods;
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
if( method < methodOffset ) if( method < methodOffset )
{ {
@@ -258,19 +248,27 @@ public class HTTPRequest implements Runnable
case 0: case 0:
{ {
// getResponseCode // getResponseCode
return new Object[]{ responseCode }; return MethodResult.of( responseCode );
} }
case 1: case 1:
{ {
// getResponseHeaders // getResponseHeaders
return new Object[]{ responseHeaders }; return MethodResult.of( responseHeaders );
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}; };
} }

View File

@@ -7,15 +7,11 @@
package dan200.computercraft.core.apis.http; package dan200.computercraft.core.apis.http;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.HTTPAPI; import dan200.computercraft.core.apis.HTTPAPI;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.StringUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
@@ -28,8 +24,6 @@ import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable
{ {
public static final String SUCCESS_EVENT = "websocket_success"; public static final String SUCCESS_EVENT = "websocket_success";
@@ -160,36 +154,48 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
@Nullable @Nullable
@Override @Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
case 0: case 0:
while( true ) checkOpen();
return MethodResult.pullEvent( MESSAGE_EVENT, new ILuaFunction()
{ {
checkOpen(); @Nonnull
Object[] event = context.pullEvent( MESSAGE_EVENT ); @Override
if( event.length >= 3 && Objects.equal( event[1], url ) ) public MethodResult call( @Nullable Object[] event ) throws LuaException
{ {
return new Object[]{ event[2] }; if( event != null && event.length >= 3 && Objects.equal( event[1], url ) )
{
return MethodResult.of( event[2] );
}
checkOpen();
return MethodResult.pullEvent( MESSAGE_EVENT, this );
} }
} } );
case 1: case 1:
{ {
checkOpen(); checkOpen();
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : ""; String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
boolean binary = optBoolean(arguments, 1, false);
computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() ); computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
channel.writeAndFlush( binary channel.writeAndFlush( new TextWebSocketFrame( text ) );
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) ) return MethodResult.empty();
: new TextWebSocketFrame( text ) );
return null;
} }
case 2: case 2:
close( true ); close( true );
return null; return MethodResult.empty();
default: default:
return null; return MethodResult.empty();
} }
} }

View File

@@ -265,10 +265,18 @@ public class Computer
@Nullable @Nullable
@Override @Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{ {
return delegate.callMethod( context, method, arguments ); return delegate.callMethod( context, method, arguments );
} }
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return delegate.callMethod( context, method, arguments );
}
} }
private static IMount s_romMount = null; private static IMount s_romMount = null;
@@ -960,7 +968,7 @@ public class Computer
return; return;
} }
} }
final Computer computer = this; final Computer computer = this;
ITask task = new ITask() { ITask task = new ITask() {
@Override @Override

View File

@@ -8,14 +8,13 @@ package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.ThreadUtils;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger;
public class ComputerThread public class ComputerThread
{ {
@@ -57,8 +56,8 @@ public class ComputerThread
*/ */
private static Thread[] s_threads = null; private static Thread[] s_threads = null;
private static final ThreadFactory s_ManagerFactory = ThreadUtils.factory( "Computer-Manager" ); private static final AtomicInteger s_ManagerCounter = new AtomicInteger( 1 );
private static final ThreadFactory s_RunnerFactory = ThreadUtils.factory( "Computer-Runner" ); private static final AtomicInteger s_DelegateCounter = new AtomicInteger( 1 );
/** /**
* Start the computer thread * Start the computer thread
@@ -73,12 +72,16 @@ public class ComputerThread
s_threads = new Thread[ComputerCraft.computer_threads]; s_threads = new Thread[ComputerCraft.computer_threads];
} }
SecurityManager manager = System.getSecurityManager();
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
for( int i = 0; i < s_threads.length; i++ ) for( int i = 0; i < s_threads.length; i++ )
{ {
Thread thread = s_threads[i]; Thread thread = s_threads[i];
if( thread == null || !thread.isAlive() ) if( thread == null || !thread.isAlive() )
{ {
(s_threads[i] = s_ManagerFactory.newThread( new TaskExecutor() )).start(); thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
} }
} }
} }
@@ -185,7 +188,12 @@ public class ComputerThread
if( thread == null || !thread.isAlive() ) if( thread == null || !thread.isAlive() )
{ {
runner = new TaskRunner(); runner = new TaskRunner();
(thread = s_RunnerFactory.newThread( runner )).start();
SecurityManager manager = System.getSecurityManager();
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
Thread thread = this.thread = new Thread( group, runner, "ComputerCraft-Computer-Runner" + s_DelegateCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
} }
long start = System.nanoTime(); long start = System.nanoTime();

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -115,7 +114,6 @@ public class ComboMount implements IMount
@Nonnull @Nonnull
@Override @Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException public InputStream openForRead( @Nonnull String path ) throws IOException
{ {
for( int i=m_parts.length-1; i>=0; --i ) for( int i=m_parts.length-1; i>=0; --i )
@@ -128,19 +126,4 @@ public class ComboMount implements IMount
} }
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
for( int i=m_parts.length-1; i>=0; --i )
{
IMount part = m_parts[i];
if( part.exists( path ) && !part.isDirectory( path ) )
{
return part.openChannelForRead( path );
}
}
throw new IOException( "/" + path + ": No such file" );
}
} }

View File

@@ -9,36 +9,34 @@ package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.util.List; import java.util.List;
public class EmptyMount implements IMount public class EmptyMount implements IMount
{ {
public EmptyMount() public EmptyMount()
{ {
} }
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) public boolean exists( @Nonnull String path )
{ {
return path.isEmpty(); return path.isEmpty();
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) public boolean isDirectory( @Nonnull String path )
{ {
return path.isEmpty(); return path.isEmpty();
} }
@Override @Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) public void list( @Nonnull String path, @Nonnull List<String> contents )
{ {
} }
@Override @Override
public long getSize( @Nonnull String path ) public long getSize( @Nonnull String path )
{ {
@@ -47,17 +45,8 @@ public class EmptyMount implements IMount
@Nonnull @Nonnull
@Override @Override
@Deprecated public InputStream openForRead( @Nonnull String path )
public InputStream openForRead( @Nonnull String path ) throws IOException
{ {
throw new IOException( "/" + path + ": No such file" ); return null;
}
@Nonnull
@Override
@Deprecated
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
throw new IOException( "/" + path + ": No such file" );
} }
} }

View File

@@ -6,54 +6,68 @@
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import com.google.common.collect.Sets;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
public class FileMount implements IWritableMount public class FileMount implements IWritableMount
{ {
private static final int MINIMUM_FILE_SIZE = 500; private static int MINIMUM_FILE_SIZE = 500;
private static final Set<OpenOption> READ_OPTIONS = Collections.singleton( StandardOpenOption.READ );
private static final Set<OpenOption> WRITE_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ); private class CountingOutputStream extends OutputStream
private static final Set<OpenOption> APPEND_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND );
private class WritableCountingChannel implements WritableByteChannel
{ {
private OutputStream m_innerStream;
private final WritableByteChannel m_inner; private long m_ignoredBytesLeft;
long m_ignoredBytesLeft;
public CountingOutputStream( OutputStream innerStream, long bytesToIgnore )
WritableCountingChannel( WritableByteChannel inner, long bytesToIgnore )
{ {
m_inner = inner; m_innerStream = innerStream;
m_ignoredBytesLeft = bytesToIgnore; m_ignoredBytesLeft = bytesToIgnore;
} }
@Override
public void close() throws IOException
{
m_innerStream.close();
}
@Override
public void flush() throws IOException
{
m_innerStream.flush();
}
@Override
public void write( @Nonnull byte[] b ) throws IOException
{
count( b.length );
m_innerStream.write( b );
}
@Override
public void write( @Nonnull byte[] b, int off, int len ) throws IOException
{
count( len );
m_innerStream.write( b, off, len );
}
@Override @Override
public int write( @Nonnull ByteBuffer b ) throws IOException public void write( int b ) throws IOException
{ {
count( b.remaining() ); count( 1 );
return m_inner.write( b ); m_innerStream.write( b );
} }
void count( long n ) throws IOException private void count( long n ) throws IOException
{ {
m_ignoredBytesLeft -= n; m_ignoredBytesLeft -= n;
if( m_ignoredBytesLeft < 0 ) if( m_ignoredBytesLeft < 0 )
{ {
long newBytes = -m_ignoredBytesLeft; long newBytes = -m_ignoredBytesLeft;
m_ignoredBytesLeft = 0; m_ignoredBytesLeft = 0;
long bytesLeft = m_capacity - m_usedSpace; long bytesLeft = m_capacity - m_usedSpace;
if( newBytes > bytesLeft ) if( newBytes > bytesLeft )
{ {
@@ -65,79 +79,12 @@ public class FileMount implements IWritableMount
} }
} }
} }
@Override
public boolean isOpen()
{
return m_inner.isOpen();
}
@Override
public void close() throws IOException
{
m_inner.close();
}
} }
private class SeekableCountingChannel extends WritableCountingChannel implements SeekableByteChannel
{
private final SeekableByteChannel m_inner;
SeekableCountingChannel( SeekableByteChannel inner, long bytesToIgnore )
{
super( inner, bytesToIgnore );
this.m_inner = inner;
}
@Override
public SeekableByteChannel position( long newPosition ) throws IOException
{
if( !isOpen() ) throw new ClosedChannelException();
if( newPosition < 0 ) throw new IllegalArgumentException();
long delta = newPosition - m_inner.position();
if( delta < 0 )
{
m_ignoredBytesLeft -= delta;
}
else
{
count( delta );
}
return m_inner.position( newPosition );
}
@Override
public SeekableByteChannel truncate( long size ) throws IOException
{
throw new IOException( "Not yet implemented" );
}
@Override
public int read( ByteBuffer dst ) throws IOException
{
if( !m_inner.isOpen() ) throw new ClosedChannelException();
throw new NonReadableChannelException();
}
@Override
public long position() throws IOException
{
return m_inner.position();
}
@Override
public long size() throws IOException
{
return m_inner.size();
}
}
private File m_rootPath; private File m_rootPath;
private long m_capacity; private long m_capacity;
private long m_usedSpace; private long m_usedSpace;
public FileMount( File rootPath, long capacity ) public FileMount( File rootPath, long capacity )
{ {
m_rootPath = rootPath; m_rootPath = rootPath;
@@ -146,7 +93,7 @@ public class FileMount implements IWritableMount
} }
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) public boolean exists( @Nonnull String path )
{ {
@@ -160,7 +107,7 @@ public class FileMount implements IWritableMount
return file.exists(); return file.exists();
} }
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) public boolean isDirectory( @Nonnull String path )
{ {
@@ -174,7 +121,7 @@ public class FileMount implements IWritableMount
return file.exists() && file.isDirectory(); return file.exists() && file.isDirectory();
} }
} }
@Override @Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{ {
@@ -203,7 +150,7 @@ public class FileMount implements IWritableMount
{ {
throw new IOException( "/" + path + ": Not a directory" ); throw new IOException( "/" + path + ": Not a directory" );
} }
} }
} }
@Override @Override
@@ -233,10 +180,9 @@ public class FileMount implements IWritableMount
} }
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
@Nonnull @Nonnull
@Override @Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException public InputStream openForRead( @Nonnull String path ) throws IOException
{ {
if( created() ) if( created() )
@@ -247,26 +193,11 @@ public class FileMount implements IWritableMount
return new FileInputStream( file ); return new FileInputStream( file );
} }
} }
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
if( created() )
{
File file = getRealPath( path );
if( file.exists() && !file.isDirectory() )
{
return FileChannel.open( file.toPath(), READ_OPTIONS );
}
}
throw new IOException( "/" + path + ": No such file" );
}
// IWritableMount implementation // IWritableMount implementation
@Override @Override
public void makeDirectory( @Nonnull String path ) throws IOException public void makeDirectory( @Nonnull String path ) throws IOException
{ {
@@ -293,7 +224,7 @@ public class FileMount implements IWritableMount
{ {
throw new IOException( "/" + path + ": Out of space" ); throw new IOException( "/" + path + ": Out of space" );
} }
boolean success = file.mkdirs(); boolean success = file.mkdirs();
if( success ) if( success )
{ {
@@ -305,7 +236,7 @@ public class FileMount implements IWritableMount
} }
} }
} }
@Override @Override
public void delete( @Nonnull String path ) throws IOException public void delete( @Nonnull String path ) throws IOException
{ {
@@ -313,7 +244,7 @@ public class FileMount implements IWritableMount
{ {
throw new IOException( "/" + path + ": Access denied" ); throw new IOException( "/" + path + ": Access denied" );
} }
if( created() ) if( created() )
{ {
File file = getRealPath( path ); File file = getRealPath( path );
@@ -323,7 +254,7 @@ public class FileMount implements IWritableMount
} }
} }
} }
private void deleteRecursively( File file ) throws IOException private void deleteRecursively( File file ) throws IOException
{ {
// Empty directories first // Empty directories first
@@ -335,7 +266,7 @@ public class FileMount implements IWritableMount
deleteRecursively( new File( file, aChildren ) ); deleteRecursively( new File( file, aChildren ) );
} }
} }
// Then delete // Then delete
long fileSize = file.isDirectory() ? 0 : file.length(); long fileSize = file.isDirectory() ? 0 : file.length();
boolean success = file.delete(); boolean success = file.delete();
@@ -348,26 +279,10 @@ public class FileMount implements IWritableMount
throw new IOException( "Access denied" ); throw new IOException( "Access denied" );
} }
} }
@Nonnull @Nonnull
@Override @Override
@Deprecated
public OutputStream openForWrite( @Nonnull String path ) throws IOException public OutputStream openForWrite( @Nonnull String path ) throws IOException
{
return Channels.newOutputStream( openChannelForWrite( path ) );
}
@Nonnull
@Override
@Deprecated
public OutputStream openForAppend( @Nonnull String path ) throws IOException
{
return Channels.newOutputStream( openChannelForAppend( path ) );
}
@Nonnull
@Override
public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
{ {
create(); create();
File file = getRealPath( path ); File file = getRealPath( path );
@@ -393,13 +308,13 @@ public class FileMount implements IWritableMount
m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE ); m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE );
m_usedSpace += MINIMUM_FILE_SIZE; m_usedSpace += MINIMUM_FILE_SIZE;
} }
return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE ); return new CountingOutputStream( new FileOutputStream( file, false ), MINIMUM_FILE_SIZE );
} }
} }
@Nonnull @Nonnull
@Override @Override
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException public OutputStream openForAppend( @Nonnull String path ) throws IOException
{ {
if( created() ) if( created() )
{ {
@@ -414,11 +329,7 @@ public class FileMount implements IWritableMount
} }
else else
{ {
// Allowing seeking when appending is not recommended, so we use a separate channel. return new CountingOutputStream( new FileOutputStream( file, true ), Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) );
return new WritableCountingChannel(
Files.newByteChannel( file.toPath(), APPEND_OPTIONS ),
Math.max( MINIMUM_FILE_SIZE - file.length(), 0 )
);
} }
} }
else else
@@ -426,23 +337,23 @@ public class FileMount implements IWritableMount
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
} }
@Override @Override
public long getRemainingSpace() public long getRemainingSpace()
{ {
return Math.max( m_capacity - m_usedSpace, 0 ); return Math.max( m_capacity - m_usedSpace, 0 );
} }
public File getRealPath( String path ) public File getRealPath( String path )
{ {
return new File( m_rootPath, path ); return new File( m_rootPath, path );
} }
private boolean created() private boolean created()
{ {
return m_rootPath.exists(); return m_rootPath.exists();
} }
private void create() throws IOException private void create() throws IOException
{ {
if( !m_rootPath.exists() ) if( !m_rootPath.exists() )
@@ -454,7 +365,7 @@ public class FileMount implements IWritableMount
} }
} }
} }
private long measureUsedSpace( File file ) private long measureUsedSpace( File file )
{ {
if( !file.exists() ) if( !file.exists() )

View File

@@ -6,23 +6,13 @@
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IFileSystem; import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull; import java.io.*;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.util.*; import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class FileSystem public class FileSystem
@@ -156,14 +146,14 @@ public class FileSystem
} }
} }
public ReadableByteChannel openForRead( String path ) throws FileSystemException public InputStream openForRead( String path ) throws FileSystemException
{ {
path = toLocal( path ); path = toLocal( path );
try try
{ {
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) ) if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
{ {
return m_mount.openChannelForRead( path ); return m_mount.openForRead( path );
} }
else else
{ {
@@ -219,17 +209,13 @@ public class FileSystem
m_writableMount.delete( path ); m_writableMount.delete( path );
} }
} }
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e ) catch( IOException e )
{ {
throw new FileSystemException( e.getMessage() ); throw new FileSystemException( e.getMessage() );
} }
} }
public WritableByteChannel openForWrite( String path ) throws FileSystemException public OutputStream openForWrite( String path ) throws FileSystemException
{ {
if( m_writableMount == null ) if( m_writableMount == null )
{ {
@@ -252,20 +238,16 @@ public class FileSystem
m_writableMount.makeDirectory( dir ); m_writableMount.makeDirectory( dir );
} }
} }
return m_writableMount.openChannelForWrite( path ); return m_writableMount.openForWrite( path );
} }
} }
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e ) catch( IOException e )
{ {
throw new FileSystemException( e.getMessage() ); throw new FileSystemException( e.getMessage() );
} }
} }
public WritableByteChannel openForAppend( String path ) throws FileSystemException public OutputStream openForAppend( String path ) throws FileSystemException
{ {
if( m_writableMount == null ) if( m_writableMount == null )
{ {
@@ -284,7 +266,7 @@ public class FileSystem
m_writableMount.makeDirectory( dir ); m_writableMount.makeDirectory( dir );
} }
} }
return m_writableMount.openChannelForWrite( path ); return m_writableMount.openForWrite( path );
} }
else if( m_mount.isDirectory( path ) ) else if( m_mount.isDirectory( path ) )
{ {
@@ -292,13 +274,9 @@ public class FileSystem
} }
else else
{ {
return m_writableMount.openChannelForAppend( path ); return m_writableMount.openForAppend( path );
} }
} }
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e ) catch( IOException e )
{ {
throw new FileSystemException( e.getMessage() ); throw new FileSystemException( e.getMessage() );
@@ -313,12 +291,10 @@ public class FileSystem
} }
} }
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this ); private final FileSystemMount m_wrapper = new FileSystemMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>(); private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() );
private final HashMap<WeakReference<FileSystemWrapper<?>>, Closeable> m_openFiles = new HashMap<>();
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException
{ {
mount( rootLabel, "", rootMount ); mount( rootLabel, "", rootMount );
@@ -334,15 +310,24 @@ public class FileSystem
// Close all dangling open files // Close all dangling open files
synchronized( m_openFiles ) synchronized( m_openFiles )
{ {
for( Closeable file : m_openFiles.values() ) closeQuietly( file ); for( Closeable file : m_openFiles )
{
try {
file.close();
} catch (IOException e) {
// Ignore
}
}
m_openFiles.clear(); m_openFiles.clear();
while( m_openFileQueue.poll() != null ) ;
} }
} }
public synchronized void mount( String label, String location, IMount mount ) throws FileSystemException public synchronized void mount( String label, String location, IMount mount ) throws FileSystemException
{ {
if( mount == null ) throw new NullPointerException(); if( mount == null )
{
throw new NullPointerException();
}
location = sanitizePath( location ); location = sanitizePath( location );
if( location.contains( ".." ) ) { if( location.contains( ".." ) ) {
throw new FileSystemException( "Cannot mount below the root" ); throw new FileSystemException( "Cannot mount below the root" );
@@ -363,18 +348,24 @@ public class FileSystem
} }
mount( new MountWrapper( label, location, mount ) ); mount( new MountWrapper( label, location, mount ) );
} }
private synchronized void mount( MountWrapper wrapper ) private synchronized void mount( MountWrapper wrapper )
{ {
String location = wrapper.getLocation(); String location = wrapper.getLocation();
m_mounts.remove( location ); if( m_mounts.containsKey( location ) )
{
m_mounts.remove( location );
}
m_mounts.put( location, wrapper ); m_mounts.put( location, wrapper );
} }
public synchronized void unmount( String path ) public synchronized void unmount( String path )
{ {
path = sanitizePath( path ); path = sanitizePath( path );
m_mounts.remove( path ); if( m_mounts.containsKey( path ) )
{
m_mounts.remove( path );
}
} }
public synchronized String combine( String path, String childPath ) public synchronized String combine( String path, String childPath )
@@ -608,85 +599,108 @@ public class FileSystem
else else
{ {
// Copy a file: // Copy a file:
try( ReadableByteChannel source = sourceMount.openForRead( sourcePath ); InputStream source = null;
WritableByteChannel destination = destinationMount.openForWrite( destinationPath ) ) OutputStream destination = null;
try
{ {
// Open both files
source = sourceMount.openForRead( sourcePath );
destination = destinationMount.openForWrite( destinationPath );
// Copy bytes as fast as we can // Copy bytes as fast as we can
ByteStreams.copy( source, destination ); byte[] buffer = new byte[1024];
} while( true )
catch( AccessDeniedException e ) {
{ int bytesRead = source.read( buffer );
throw new FileSystemException( "Access denied" ); if( bytesRead >= 0 )
{
destination.write( buffer, 0, bytesRead );
}
else
{
break;
}
}
} }
catch( IOException e ) catch( IOException e )
{ {
throw new FileSystemException( e.getMessage() ); throw new FileSystemException( e.getMessage() );
} }
} finally
}
private void cleanup()
{
synchronized( m_openFiles )
{
Reference<?> ref;
while( (ref = m_openFileQueue.poll()) != null )
{ {
Closeable file = m_openFiles.remove( ref ); // Close both files
if( file != null ) closeQuietly( file ); if( source != null )
{
try {
source.close();
} catch( IOException e ) {
// nobody cares
}
}
if( destination != null )
{
try {
destination.close();
} catch( IOException e ) {
// nobody cares
}
}
} }
} }
} }
private synchronized <T extends Closeable> FileSystemWrapper<T> openFile( @Nonnull T file ) throws FileSystemException private synchronized <T> T openFile( T file, Closeable handle ) throws FileSystemException
{ {
synchronized( m_openFiles ) synchronized( m_openFiles )
{ {
if( ComputerCraft.maximumFilesOpen > 0 && if( ComputerCraft.maximumFilesOpen > 0 &&
m_openFiles.size() >= ComputerCraft.maximumFilesOpen ) m_openFiles.size() >= ComputerCraft.maximumFilesOpen )
{ {
closeQuietly( file ); if( handle != null )
throw new FileSystemException( "Too many files already open" ); {
try {
handle.close();
} catch ( IOException ignored ) {
// shrug
}
}
throw new FileSystemException("Too many files already open");
} }
FileSystemWrapper<T> wrapper = new FileSystemWrapper<>( this, file, m_openFileQueue ); m_openFiles.add( handle );
m_openFiles.put( wrapper.self, file ); return file;
return wrapper;
} }
} }
synchronized void removeFile( FileSystemWrapper<?> handle ) private synchronized void closeFile( Closeable handle ) throws IOException
{ {
synchronized( m_openFiles ) synchronized( m_openFiles )
{ {
m_openFiles.remove( handle.self ); m_openFiles.remove( handle );
handle.close();
} }
} }
public synchronized <T extends Closeable> FileSystemWrapper<T> openForRead( String path, Function<ReadableByteChannel, T> open ) throws FileSystemException public synchronized InputStream openForRead( String path ) throws FileSystemException
{ {
cleanup(); path = sanitizePath ( path );
path = sanitizePath( path );
MountWrapper mount = getMount( path ); MountWrapper mount = getMount( path );
ReadableByteChannel channel = mount.openForRead( path ); InputStream stream = mount.openForRead( path );
if( channel != null ) if( stream != null )
{ {
return openFile( open.apply( channel ) ); return openFile( new ClosingInputStream( stream ), stream );
} }
return null; return null;
} }
public synchronized <T extends Closeable> FileSystemWrapper<T> openForWrite( String path, boolean append, Function<WritableByteChannel, T> open ) throws FileSystemException public synchronized OutputStream openForWrite( String path, boolean append ) throws FileSystemException
{ {
cleanup(); path = sanitizePath ( path );
path = sanitizePath( path );
MountWrapper mount = getMount( path ); MountWrapper mount = getMount( path );
WritableByteChannel channel = append ? mount.openForAppend( path ) : mount.openForWrite( path ); OutputStream stream = append ? mount.openForAppend( path ) : mount.openForWrite( path );
if( channel != null ) if( stream != null )
{ {
return openFile( open.apply( channel ) ); return openFile( new ClosingOutputStream( stream ), stream );
} }
return null; return null;
} }
@@ -851,14 +865,33 @@ public class FileSystem
} }
} }
private static void closeQuietly( Closeable c ) private class ClosingInputStream extends FilterInputStream
{ {
try protected ClosingInputStream( InputStream in )
{ {
c.close(); super( in );
} }
catch( IOException ignored )
@Override
public void close() throws IOException
{ {
super.close();
closeFile( in );
}
}
private class ClosingOutputStream extends FilterOutputStream
{
protected ClosingOutputStream( OutputStream out )
{
super( out );
}
@Override
public void close() throws IOException
{
super.close();
closeFile( out );
} }
} }
} }

View File

@@ -1,132 +1,185 @@
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IFileSystem;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.ReadableByteChannel; import java.io.OutputStream;
import java.nio.file.FileSystem; import java.util.Collections;
import java.nio.file.*; import java.util.List;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Stream;
public class FileSystemMount implements IMount public class FileSystemMount implements IFileSystem
{ {
private final Entry rootEntry; private final FileSystem m_filesystem;
public FileSystemMount( FileSystem fileSystem, String root ) throws IOException public FileSystemMount( FileSystem m_filesystem )
{ {
Path rootPath = fileSystem.getPath( root ); this.m_filesystem = m_filesystem;
rootEntry = new Entry( "", rootPath ); }
Queue<Entry> entries = new ArrayDeque<>(); @Override
entries.add( rootEntry ); public void makeDirectory( @Nonnull String path ) throws IOException
while( !entries.isEmpty() ) {
try
{ {
Entry entry = entries.remove(); m_filesystem.makeDir( path );
try( Stream<Path> childStream = Files.list( entry.path ) ) }
{ catch( FileSystemException e )
Iterator<Path> children = childStream.iterator(); {
while( children.hasNext() ) throw new IOException( e.getMessage() );
{
Path childPath = children.next();
Entry child = new Entry( childPath.getFileName().toString(), childPath );
entry.children.put( child.name, child );
if( child.directory ) entries.add( child );
}
}
} }
} }
@Override @Override
public boolean exists( @Nonnull String path ) public void delete( @Nonnull String path ) throws IOException
{ {
return getFile( path ) != null; try
{
m_filesystem.delete( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public OutputStream openForWrite( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, false );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public OutputStream openForAppend( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, true );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) public long getRemainingSpace() throws IOException
{ {
Entry entry = getFile( path ); try
return entry != null && entry.directory; {
return m_filesystem.getFreeSpace( "/" );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
} }
@Override @Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{ {
Entry entry = getFile( path ); try
if( entry == null || !entry.directory ) throw new IOException( "/" + path + ": Not a directory" ); {
Collections.addAll( contents, m_filesystem.list( path ) );
contents.addAll( entry.children.keySet() ); }
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
} }
@Override @Override
public long getSize( @Nonnull String path ) throws IOException public long getSize( @Nonnull String path ) throws IOException
{ {
Entry file = getFile( path ); try
if( file == null ) throw new IOException( "/" + path + ": No such file" ); {
return file.size; return m_filesystem.getSize( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
} }
@Nonnull @Nonnull
@Override @Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException public InputStream openForRead( @Nonnull String path ) throws IOException
{ {
Entry file = getFile( path ); try
if( file == null || file.directory ) throw new IOException( "/" + path + ": No such file" );
return Files.newInputStream( file.path, StandardOpenOption.READ );
}
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
Entry file = getFile( path );
if( file == null || file.directory ) throw new IOException( "/" + path + ": No such file" );
return Files.newByteChannel( file.path, StandardOpenOption.READ );
}
private Entry getFile( String path )
{
if( path.equals( "" ) ) return rootEntry;
if( !path.contains( "/" ) ) return rootEntry.children.get( path );
String[] components = path.split( "/" );
Entry entry = rootEntry;
for( String component : components )
{ {
if( entry == null || entry.children == null ) return null; return m_filesystem.openForRead( path );
entry = entry.children.get( component ); }
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
} }
return entry;
} }
private static class Entry @Override
public String combine( String path, String child )
{ {
final String name; return m_filesystem.combine( path, child );
final Path path; }
final boolean directory; @Override
final long size; public void copy( String from, String to ) throws IOException
final Map<String, Entry> children; {
try
private Entry( String name, Path path ) throws IOException
{ {
if( name.endsWith( "/" ) || name.endsWith( "\\" ) ) name = name.substring( 0, name.length() - 1 ); m_filesystem.copy( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
this.name = name; @Override
this.path = path; public void move( String from, String to ) throws IOException
{
BasicFileAttributes attributes = Files.readAttributes( path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS ); try
this.directory = attributes.isDirectory(); {
this.size = directory ? 0 : attributes.size(); m_filesystem.move( from, to );
this.children = directory ? new HashMap<>() : null; }
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
} }
} }
} }

View File

@@ -1,42 +0,0 @@
package dan200.computercraft.core.filesystem;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* An alternative closeable implementation that will free up resources in the filesystem.
*
* In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks
* on the stream, it's not really possible as it'd require numerous instances.
*
* @param <T> The stream to wrap.
*/
public class FileSystemWrapper<T extends Closeable> implements Closeable
{
private final FileSystem fileSystem;
private final T closeable;
final WeakReference<FileSystemWrapper<?>> self;
FileSystemWrapper( FileSystem fileSystem, T closeable, ReferenceQueue<FileSystemWrapper<?>> queue )
{
this.fileSystem = fileSystem;
this.closeable = closeable;
this.self = new WeakReference<>( this, queue );
}
@Override
public void close() throws IOException
{
fileSystem.removeFile( this );
closeable.close();
}
@Nonnull
public T get()
{
return closeable;
}
}

View File

@@ -1,220 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IFileSystem;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
public class FileSystemWrapperMount implements IFileSystem
{
private final FileSystem m_filesystem;
public FileSystemWrapperMount( FileSystem m_filesystem )
{
this.m_filesystem = m_filesystem;
}
@Override
public void makeDirectory( @Nonnull String path ) throws IOException
{
try
{
m_filesystem.makeDir( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void delete( @Nonnull String path ) throws IOException
{
try
{
m_filesystem.delete( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
try
{
// FIXME: Think of a better way of implementing this, so closing this will close on the computer.
return m_filesystem.openForRead( path, Function.identity() ).get();
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, false, Function.identity() ).get();
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, true, Function.identity() ).get();
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
return Channels.newInputStream( openChannelForRead( path ) );
}
@Nonnull
@Override
@Deprecated
public OutputStream openForWrite( @Nonnull String path ) throws IOException
{
return Channels.newOutputStream( openChannelForWrite( path ) );
}
@Nonnull
@Override
@Deprecated
public OutputStream openForAppend( @Nonnull String path ) throws IOException
{
return Channels.newOutputStream( openChannelForAppend( path ) );
}
@Override
public long getRemainingSpace() throws IOException
{
try
{
return m_filesystem.getFreeSpace( "/" );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{
try
{
Collections.addAll( contents, m_filesystem.list( path ) );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.getSize( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public String combine( String path, String child )
{
return m_filesystem.combine( path, child );
}
@Override
public void copy( String from, String to ) throws IOException
{
try
{
m_filesystem.copy( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void move( String from, String to ) throws IOException
{
try
{
m_filesystem.move( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
}

View File

@@ -20,16 +20,15 @@ import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@Deprecated
public class JarMount implements IMount public class JarMount implements IMount
{ {
private static class FileInZip private class FileInZip
{ {
private String m_path; private String m_path;
private boolean m_directory; private boolean m_directory;
private long m_size; private long m_size;
private Map<String, FileInZip> m_children; private Map<String, FileInZip> m_children;
public FileInZip( String path, boolean directory, long size ) public FileInZip( String path, boolean directory, long size )
{ {
m_path = path; m_path = path;
@@ -37,44 +36,44 @@ public class JarMount implements IMount
m_size = m_directory ? 0 : size; m_size = m_directory ? 0 : size;
m_children = new LinkedHashMap<>(); m_children = new LinkedHashMap<>();
} }
public String getPath() public String getPath()
{ {
return m_path; return m_path;
} }
public boolean isDirectory() public boolean isDirectory()
{ {
return m_directory; return m_directory;
} }
public long getSize() public long getSize()
{ {
return m_size; return m_size;
} }
public void list( List<String> contents ) public void list( List<String> contents )
{ {
contents.addAll( m_children.keySet() ); contents.addAll( m_children.keySet() );
} }
public void insertChild( FileInZip child ) public void insertChild( FileInZip child )
{ {
String localPath = FileSystem.toLocal( child.getPath(), m_path ); String localPath = FileSystem.toLocal( child.getPath(), m_path );
m_children.put( localPath, child ); m_children.put( localPath, child );
} }
public FileInZip getFile( String path ) public FileInZip getFile( String path )
{ {
// If we've reached the target, return this // If we've reached the target, return this
if( path.equals( m_path ) ) if( path.equals( m_path ) )
{ {
return this; return this;
} }
// Otherwise, get the next component of the path // Otherwise, get the next component of the path
String localPath = FileSystem.toLocal( path, m_path ); String localPath = FileSystem.toLocal( path, m_path );
int slash = localPath.indexOf( "/" ); int slash = localPath.indexOf("/");
if( slash >= 0 ) if( slash >= 0 )
{ {
localPath = localPath.substring( 0, slash ); localPath = localPath.substring( 0, slash );
@@ -86,17 +85,17 @@ public class JarMount implements IMount
{ {
return subFile.getFile( path ); return subFile.getFile( path );
} }
return null; return null;
} }
public FileInZip getParent( String path ) public FileInZip getParent( String path )
{ {
if( path.length() == 0 ) if( path.length() == 0 )
{ {
return null; return null;
} }
FileInZip file = getFile( FileSystem.getDirectory( path ) ); FileInZip file = getFile( FileSystem.getDirectory( path ) );
if( file.isDirectory() ) if( file.isDirectory() )
{ {
@@ -105,19 +104,18 @@ public class JarMount implements IMount
return null; return null;
} }
} }
private ZipFile m_zipFile; private ZipFile m_zipFile;
private FileInZip m_root; private FileInZip m_root;
private String m_rootPath; private String m_rootPath;
@Deprecated
public JarMount( File jarFile, String subPath ) throws IOException public JarMount( File jarFile, String subPath ) throws IOException
{ {
if( !jarFile.exists() || jarFile.isDirectory() ) if( !jarFile.exists() || jarFile.isDirectory() )
{ {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
// Open the zip file // Open the zip file
try try
{ {
@@ -127,13 +125,13 @@ public class JarMount implements IMount
{ {
throw new IOException( "Error loading zip file" ); throw new IOException( "Error loading zip file" );
} }
if( m_zipFile.getEntry( subPath ) == null ) if( m_zipFile.getEntry( subPath ) == null )
{ {
m_zipFile.close(); m_zipFile.close();
throw new IOException( "Zip does not contain path" ); throw new IOException( "Zip does not contain path" );
} }
// Read in all the entries // Read in all the entries
Enumeration<? extends ZipEntry> zipEntries = m_zipFile.entries(); Enumeration<? extends ZipEntry> zipEntries = m_zipFile.entries();
while( zipEntries.hasMoreElements() ) while( zipEntries.hasMoreElements() )
@@ -141,7 +139,7 @@ public class JarMount implements IMount
ZipEntry entry = zipEntries.nextElement(); ZipEntry entry = zipEntries.nextElement();
String entryName = entry.getName(); String entryName = entry.getName();
if( entryName.startsWith( subPath ) ) if( entryName.startsWith( subPath ) )
{ {
entryName = FileSystem.toLocal( entryName, subPath ); entryName = FileSystem.toLocal( entryName, subPath );
if( m_root == null ) if( m_root == null )
{ {
@@ -171,19 +169,19 @@ public class JarMount implements IMount
// TODO: handle this case. The code currently assumes we find folders before their contents // TODO: handle this case. The code currently assumes we find folders before their contents
} }
} }
} }
} }
} }
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) public boolean exists( @Nonnull String path )
{ {
FileInZip file = m_root.getFile( path ); FileInZip file = m_root.getFile( path );
return file != null; return file != null;
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) public boolean isDirectory( @Nonnull String path )
{ {
@@ -194,7 +192,7 @@ public class JarMount implements IMount
} }
return false; return false;
} }
@Override @Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{ {
@@ -205,10 +203,10 @@ public class JarMount implements IMount
} }
else else
{ {
throw new IOException( "/" + path + ": Not a directory" ); throw new IOException( "/" + path + ": Not a directory" );
} }
} }
@Override @Override
public long getSize( @Nonnull String path ) throws IOException public long getSize( @Nonnull String path ) throws IOException
{ {
@@ -217,7 +215,7 @@ public class JarMount implements IMount
{ {
return file.getSize(); return file.getSize();
} }
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
@Nonnull @Nonnull
@@ -245,6 +243,6 @@ public class JarMount implements IMount
// treat errors as non-existance of file // treat errors as non-existance of file
} }
} }
throw new IOException( "/" + path + ": No such file" ); throw new IOException( "/" + path + ": No such file" );
} }
} }

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.channels.ReadableByteChannel;
import java.util.List; import java.util.List;
public class SubMount implements IMount public class SubMount implements IMount
@@ -53,19 +52,11 @@ public class SubMount implements IMount
@Nonnull @Nonnull
@Override @Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException public InputStream openForRead( @Nonnull String path ) throws IOException
{ {
return m_parent.openForRead( getFullPath( path ) ); return m_parent.openForRead( getFullPath( path ) );
} }
@Nonnull
@Override
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return m_parent.openChannelForRead( getFullPath( path ) );
}
private String getFullPath( String path ) private String getFullPath( String path )
{ {
if( path.length() == 0 ) if( path.length() == 0 )

View File

@@ -0,0 +1,87 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import javax.annotation.Nonnull;
class CobaltCallContext implements ICallContext
{
private final Computer computer;
CobaltCallContext( Computer computer )
{
this.computer = computer;
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
computer.queueEvent( "task_complete", eventArguments );
}
else
{
computer.queueEvent( "task_complete", new Object[]{ taskID, true } );
}
}
catch( LuaException e )
{
computer.queueEvent( "task_complete", new Object[]{
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
computer.queueEvent( "task_complete", new Object[]{
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaContextTask;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import org.squiddev.cobalt.LuaError;
import org.squiddev.cobalt.LuaState;
import org.squiddev.cobalt.LuaThread;
import org.squiddev.cobalt.UnwindThrowable;
import javax.annotation.Nonnull;
import java.lang.ref.WeakReference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An ugly wrapper for {@link ILuaContext} style calls, which executes them on a separate thread.
*/
class CobaltLuaContext extends CobaltCallContext implements ILuaContext
{
private static final ThreadGroup group = new ThreadGroup( "ComputerCraft-Lua" );
private static final AtomicInteger threadCounter = new AtomicInteger();
private static final ExecutorService threads = new ThreadPoolExecutor(
4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(),
task -> {
Thread thread = new Thread( group, task, group.getName() + "-" + threadCounter.incrementAndGet() );
if( !thread.isDaemon() ) thread.setDaemon( true );
if( thread.getPriority() != Thread.NORM_PRIORITY ) thread.setPriority( Thread.NORM_PRIORITY );
return thread;
}
);
private boolean done = false;
private Object[] values;
private LuaError exception;
private final Semaphore yield = new Semaphore();
private final Semaphore resume = new Semaphore();
private WeakReference<LuaThread> thread;
CobaltLuaContext( Computer computer, LuaState state )
{
super( computer );
this.thread = state.getCurrentThread().getReference();
}
@Nonnull
@Override
@Deprecated
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
@Deprecated
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[]{ filter } );
}
@Nonnull
@Override
@Deprecated
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
if( done ) throw new IllegalStateException( "Cannot yield when complete" );
values = yieldArgs;
yield.signal();
// Every 30 seconds check to see if the coroutine has been GCed
// if so then abort this task.
while( !resume.await( 30000 ) )
{
if( thread.get() == null ) throw new InterruptedException( "Orphaned async task" );
}
return values;
}
@Override
@Deprecated
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
{
if( ((Number) response[1]).intValue() == taskID )
{
Object[] returnValues = new Object[response.length - 3];
if( (Boolean) response[2] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String )
{
throw new LuaException( (String) response[3] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
void execute( ILuaContextTask task )
{
threads.submit( () -> {
try
{
values = task.execute( this );
}
catch( LuaException e )
{
exception = new LuaError( e.getMessage(), e.getLevel() );
}
catch( InterruptedException e )
{
exception = new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
finally
{
done = true;
yield.signal();
}
} );
}
void resume( Object[] args )
{
values = args;
resume.signal();
}
Object[] await( LuaState state, CobaltLuaMachine machine ) throws LuaError, UnwindThrowable
{
if( !done )
{
try
{
yield.await();
}
catch( InterruptedException e )
{
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
}
if( done )
{
if( exception != null ) throw exception;
return values;
}
else
{
LuaThread.yield( state, machine.toValues( values ) );
throw new IllegalStateException( "Unreachable" );
}
}
}

View File

@@ -7,13 +7,9 @@
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.ThreadUtils;
import org.squiddev.cobalt.*; import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException; import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState; import org.squiddev.cobalt.compiler.LoadState;
@@ -24,9 +20,8 @@ import org.squiddev.cobalt.function.LibFunction;
import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.LuaFunction;
import org.squiddev.cobalt.function.VarArgFunction; import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*; import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator; import org.squiddev.cobalt.lib.platform.AbstractResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -34,9 +29,6 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.squiddev.cobalt.Constants.NONE; import static org.squiddev.cobalt.Constants.NONE;
import static org.squiddev.cobalt.ValueFactory.valueOf; import static org.squiddev.cobalt.ValueFactory.valueOf;
@@ -44,19 +36,12 @@ import static org.squiddev.cobalt.ValueFactory.varargsOf;
public class CobaltLuaMachine implements ILuaMachine public class CobaltLuaMachine implements ILuaMachine
{ {
private static final ThreadPoolExecutor coroutines = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
ThreadUtils.factory( "Coroutine" )
);
private final Computer m_computer; private final Computer m_computer;
private LuaState m_state; private final LuaState m_state;
private LuaTable m_globals; private final LuaTable m_globals;
private LuaThread m_mainRoutine;
private LuaThread m_mainRoutine;
private String m_eventFilter; private String m_eventFilter;
private String m_softAbortMessage; private String m_softAbortMessage;
private String m_hardAbortMessage; private String m_hardAbortMessage;
@@ -66,71 +51,60 @@ public class CobaltLuaMachine implements ILuaMachine
m_computer = computer; m_computer = computer;
// Create an environment to run in // Create an environment to run in
LuaState state = this.m_state = LuaState.builder() final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
.resourceManipulator( new VoidResourceManipulator() ) {
.debug( new DebugHandler() @Override
public InputStream findResource( String filename )
{ {
private int count = 0; return null;
private boolean hasSoftAbort; }
} );
state.debug = new DebugHandler( state )
{
private int count = 0;
private boolean hasSoftAbort;
@Override @Override
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
{
int count = ++this.count;
if( count > 100000 )
{ {
int count = ++this.count; if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
if( count > 100000 ) this.count = 0;
{
if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE );
this.count = 0;
}
else
{
handleSoftAbort();
}
super.onInstruction( ds, di, pc, extras, top );
} }
else
@Override
public void poll() throws LuaError
{ {
if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE );
handleSoftAbort(); handleSoftAbort();
} }
private void handleSoftAbort() throws LuaError super.onInstruction( ds, di, pc, extras, top );
{ }
// If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if( message == null )
{
hasSoftAbort = false;
return;
}
if( hasSoftAbort && m_hardAbortMessage == null ) @Override
{ public void poll() throws LuaError
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK. {
return; if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
} handleSoftAbort();
}
hasSoftAbort = true; private void handleSoftAbort() throws LuaError {
throw new LuaError( message ); // If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if (message == null) {
hasSoftAbort = false;
return;
} }
} )
.coroutineFactory( command -> { if (hasSoftAbort && m_hardAbortMessage == null) {
Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 ); // If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
coroutines.execute( () -> { return;
try }
{
command.run(); hasSoftAbort = true;
} throw new LuaError(message);
finally }
{ };
Tracking.addValue( m_computer, TrackingField.COROUTINES_DISPOSED, 1 );
}
} );
} )
.build();
m_globals = new LuaTable(); m_globals = new LuaTable();
state.setupThread( m_globals ); state.setupThread( m_globals );
@@ -145,10 +119,10 @@ public class CobaltLuaMachine implements ILuaMachine
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() ); if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
// Register custom load/loadstring provider which automatically adds prefixes. // Register custom load/loadstring provider which automatically adds prefixes.
LibFunction.bind( state, m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } ); LibFunction.bind( m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } );
// Remove globals we don't want to expose // Remove globals we don't want to expose
// m_globals.rawset( "collectgarbage", Constants.NIL ); m_globals.rawset( "collectgarbage", Constants.NIL );
m_globals.rawset( "dofile", Constants.NIL ); m_globals.rawset( "dofile", Constants.NIL );
m_globals.rawset( "loadfile", Constants.NIL ); m_globals.rawset( "loadfile", Constants.NIL );
m_globals.rawset( "print", Constants.NIL ); m_globals.rawset( "print", Constants.NIL );
@@ -186,7 +160,10 @@ public class CobaltLuaMachine implements ILuaMachine
public void loadBios( InputStream bios ) public void loadBios( InputStream bios )
{ {
// Begin executing a file (ie, the bios) // Begin executing a file (ie, the bios)
if( m_mainRoutine != null ) return; if( m_mainRoutine != null )
{
return;
}
try try
{ {
@@ -195,19 +172,30 @@ public class CobaltLuaMachine implements ILuaMachine
} }
catch( CompileException e ) catch( CompileException e )
{ {
unload(); if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
} }
catch( IOException e ) catch( IOException e )
{ {
ComputerCraft.log.warn( "Could not load bios.lua ", e ); ComputerCraft.log.warn( "Could not load bios.lua ", e );
unload(); if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
} }
} }
@Override @Override
public void handleEvent( String eventName, Object[] arguments ) public void handleEvent( String eventName, Object[] arguments )
{ {
if( m_mainRoutine == null ) return; if( m_mainRoutine == null )
{
return;
}
if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) ) if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) )
{ {
@@ -222,26 +210,34 @@ public class CobaltLuaMachine implements ILuaMachine
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
} }
Varargs results = m_mainRoutine.resume( resumeArgs ); LuaValue filter = LuaThread.run( m_mainRoutine, resumeArgs ).first();
if( m_hardAbortMessage != null ) if( m_hardAbortMessage != null )
{ {
throw new LuaError( m_hardAbortMessage ); throw new LuaError( m_hardAbortMessage );
} }
else if( !results.first().checkBoolean() )
{
throw new LuaError( results.arg( 2 ).checkString() );
}
else else
{ {
LuaValue filter = results.arg( 2 ); if( filter.isString() )
m_eventFilter = filter.isString() ? filter.toString() : null; {
m_eventFilter = filter.toString();
}
else
{
m_eventFilter = null;
}
} }
if( m_mainRoutine.getStatus().equals( "dead" ) ) unload(); LuaThread mainThread = m_mainRoutine;
if( mainThread.getStatus().equals( "dead" ) )
{
m_mainRoutine = null;
}
} }
catch( LuaError e ) catch( LuaError e )
{ {
unload(); if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Main thread crashed", e );
m_mainRoutine.abandon();
m_mainRoutine = null;
} }
finally finally
{ {
@@ -278,199 +274,30 @@ public class CobaltLuaMachine implements ILuaMachine
@Override @Override
public boolean isFinished() public boolean isFinished()
{ {
return m_mainRoutine == null; return (m_mainRoutine == null);
} }
@Override @Override
public void unload() public void unload()
{ {
if( m_state == null ) return; if( m_mainRoutine != null )
{
m_state.abandon(); LuaThread mainThread = m_mainRoutine;
m_mainRoutine = null; mainThread.abandon();
m_state = null; m_mainRoutine = null;
m_globals = null; }
} }
private LuaTable wrapLuaObject( ILuaObject object ) private LuaTable wrapLuaObject( ILuaObject object )
{ {
LuaTable table = new LuaTable(); LuaTable table = new LuaTable();
String[] methods = object.getMethodNames(); String[] methods = object.getMethodNames();
for( int i = 0; i < methods.length; ++i ) for( int method = 0; method < methods.length; method++ )
{ {
if( methods[ i ] != null ) if( methods[method] != null )
{ {
final int method = i; final String methodName = methods[method];
final ILuaObject apiObject = object; table.rawset( methodName, new CobaltWrapperFunction( this, m_computer, object, method, methodName ) );
final String methodName = methods[ i ];
table.rawset( methodName, new VarArgFunction()
{
@Override
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
{
Object[] arguments = toObjects( _args, 1 );
Object[] results;
try
{
results = apiObject.callMethod( new ILuaContext()
{
@Nonnull
@Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[ 0 ].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
@Nonnull
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
try
{
Varargs results = LuaThread.yield( state, toValues( yieldArgs ) );
return toObjects( results, 1 );
}
catch( OrphanedThread e )
{
throw new InterruptedException();
}
catch( Throwable e )
{
throw new RuntimeException( e );
}
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return m_computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[ results.length + 2 ];
eventArguments[ 0 ] = taskID;
eventArguments[ 1 ] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
@Override
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean )
{
if( ((Number) response[ 1 ]).intValue() == taskID )
{
Object[] returnValues = new Object[ response.length - 3 ];
if( (Boolean) response[ 2 ] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[ 3 ] instanceof String )
{
throw new LuaException( (String) response[ 3 ] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
}, method, arguments );
}
catch( InterruptedException e )
{
throw new OrphanedThread();
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
}
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
}
return toValues( results );
}
} );
} }
} }
return table; return table;
@@ -538,7 +365,7 @@ public class CobaltLuaMachine implements ILuaMachine
} }
} }
private Varargs toValues( Object[] objects ) Varargs toValues( Object[] objects )
{ {
if( objects == null || objects.length == 0 ) if( objects == null || objects.length == 0 )
{ {
@@ -629,7 +456,7 @@ public class CobaltLuaMachine implements ILuaMachine
} }
} }
private static Object[] toObjects( Varargs values, int startIdx ) static Object[] toObjects( Varargs values, int startIdx )
{ {
int count = values.count(); int count = values.count();
Object[] objects = new Object[ count - startIdx + 1 ]; Object[] objects = new Object[ count - startIdx + 1 ];
@@ -685,7 +512,7 @@ public class CobaltLuaMachine implements ILuaMachine
private byte[] bytes; private byte[] bytes;
private int offset, remaining = 0; private int offset, remaining = 0;
StringInputStream( LuaState state, LuaValue func ) public StringInputStream( LuaState state, LuaValue func )
{ {
this.state = state; this.state = state;
this.func = func; this.func = func;
@@ -697,13 +524,23 @@ public class CobaltLuaMachine implements ILuaMachine
if( remaining <= 0 ) if( remaining <= 0 )
{ {
LuaValue s; LuaValue s;
state.getCurrentThread().disableYield();
try try
{ {
s = OperationHelper.call( state, func ); s = OperationHelper.call( state, func );
} catch (LuaError e) }
catch( UnwindThrowable e )
{
throw new IOException( "Impossible yield within load" );
}
catch( LuaError e )
{ {
throw new IOException( e ); throw new IOException( e );
} }
finally
{
state.getCurrentThread().enableYield();
}
if( s.isNil() ) if( s.isNil() )
{ {
@@ -729,4 +566,5 @@ public class CobaltLuaMachine implements ILuaMachine
return bytes[offset++]; return bytes[offset++];
} }
} }
} }

View File

@@ -0,0 +1,292 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.computer.Computer;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.VarArgFunction;
import java.util.ArrayDeque;
import java.util.Deque;
class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWrapperFunction.State>
{
private final CobaltLuaMachine machine;
private final Computer computer;
private final CobaltCallContext callContext;
private final ILuaObject delegate;
private final int method;
private final String methodName;
CobaltWrapperFunction( CobaltLuaMachine machine, Computer computer, ILuaObject delegate, int method, String methodName )
{
this.machine = machine;
this.computer = computer;
this.callContext = new CobaltCallContext( computer );
this.delegate = delegate;
this.method = method;
this.methodName = methodName;
}
@Override
public Varargs invoke( final LuaState state, Varargs args ) throws LuaError, UnwindThrowable
{
MethodResult future;
try
{
future = delegate.callMethod( callContext, method, CobaltLuaMachine.toObjects( args, 1 ) );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Exception e )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e );
}
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
// Verify we've a "well formed" future
if( future == null )
{
ComputerCraft.log.error( "Null result from " + delegate );
throw new LuaError( "Java Exception Thrown: Null result" );
}
// Fast path for immediate results
if( future instanceof MethodResult.Immediate )
{
return machine.toValues( ((MethodResult.Immediate) future).getResult() );
}
State context = new State();
try
{
return runFuture( state, context, future );
}
catch( UnwindThrowable e )
{
// Push our state onto the stack if need-be. Normally this wouldn't be safe and we
// should do this at the very beginning, but we know that we won't be calling anything
// else which will push to the resume stack.
DebugState ds = state.debug.getDebugState();
state.debug.onCall( ds, this, context );
throw e;
}
}
@Override
public Varargs resume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
{
Varargs result;
try
{
result = doResume( state, context, args );
}
catch( LuaError e )
{
state.debug.onReturn();
throw e;
}
catch( Exception e )
{
state.debug.onReturn();
throw new LuaError( e );
}
state.debug.onReturn();
return result;
}
private Varargs doResume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
{
MethodResult future = context.pending;
if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
if( !onEvent.isRaw() && args.first().toString().equals( "terminate" ) )
{
throw new LuaError( "Terminated", 0 );
}
return runCallback( state, context, CobaltLuaMachine.toObjects( args, 1 ) );
}
else if( future instanceof MethodResult.OnMainThread )
{
if( args.arg( 2 ).isNumber() && args.arg( 3 ).isBoolean() && args.arg( 2 ).toLong() == context.taskId )
{
if( args.arg( 3 ).toBoolean() )
{
// Extract the return values from the event and return them
return runFuture( state, context, context.taskResult );
}
else
{
// Extract the error message from the event and raise it
throw new LuaError( args.arg( 4 ) );
}
}
else
{
LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) );
throw new IllegalStateException( "Unreachable" );
}
}
else if( future instanceof MethodResult.WithLuaContext )
{
context.luaContext.resume( CobaltLuaMachine.toObjects( args, 1 ) );
return runCallback( state, context, context.luaContext.await( state, machine ) );
}
else
{
ComputerCraft.log.error( "Unknown method result " + future );
throw new LuaError( "Java Exception Thrown: Unknown method result" );
}
}
@Override
public Varargs resumeError( LuaState state, State context, LuaError error ) throws LuaError
{
state.debug.onReturn();
throw error;
}
private Varargs runFuture( LuaState state, State context, MethodResult future ) throws LuaError, UnwindThrowable
{
Deque<ILuaFunction> callbacks = context.callbacks;
while( true )
{
if( future instanceof MethodResult.AndThen )
{
MethodResult.AndThen then = ((MethodResult.AndThen) future);
// Thens are "unwrapped", being pushed onto a stack
if( callbacks == null ) callbacks = context.callbacks = new ArrayDeque<>();
callbacks.addLast( then.getCallback() );
future = then.getPrevious();
}
else if( future instanceof MethodResult.Immediate )
{
Object[] values = ((MethodResult.Immediate) future).getResult();
// Immediate values values will attempt to call the previous "then", or return if nothing
// else needs to be done.
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return machine.toValues( values );
future = runFunction( callback, values );
}
else if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
// Mark this future as pending and yield
context.pending = future;
String filter = onEvent.getFilter();
LuaThread.yield( state, filter == null ? Constants.NIL : ValueFactory.valueOf( filter ) );
throw new IllegalStateException( "Unreachable" );
}
else if( future instanceof MethodResult.OnMainThread )
{
MethodResult.OnMainThread onMainThread = (MethodResult.OnMainThread) future;
// Mark this future as pending and yield
context.pending = future;
try
{
context.taskId = callContext.issueMainThreadTask( () -> {
context.taskResult = onMainThread.getTask().execute();
return null;
} );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) );
throw new IllegalStateException( "Unreachable" );
}
else if( future instanceof MethodResult.WithLuaContext )
{
MethodResult.WithLuaContext withContext = (MethodResult.WithLuaContext) future;
// Mark this future as pending and execute on a separate thread.
context.pending = future;
CobaltLuaContext luaContext = context.luaContext = new CobaltLuaContext( computer, state );
luaContext.execute( withContext.getConsumer() );
return runCallback( state, context, luaContext.await( state, machine ) );
}
else
{
ComputerCraft.log.error( "Unknown method result " + future );
throw new LuaError( "Java Exception Thrown: Unknown method result" );
}
}
}
private Varargs runCallback( LuaState state, State context, Object[] args ) throws LuaError, UnwindThrowable
{
Deque<ILuaFunction> callbacks = context.callbacks;
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return machine.toValues( args );
return runFuture( state, context, runFunction( callback, args ) );
}
private MethodResult runFunction( ILuaFunction func, Object[] args ) throws LuaError
{
MethodResult result;
try
{
result = func.call( args );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Exception e )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e );
}
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
if( result == null )
{
ComputerCraft.log.error( "Null result from " + func );
throw new LuaError( "Java Exception Thrown: Null result" );
}
return result;
}
static class State
{
Deque<ILuaFunction> callbacks;
MethodResult pending;
CobaltLuaContext luaContext;
long taskId;
MethodResult taskResult;
}
}

View File

@@ -0,0 +1,38 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
/**
* A trivial way of signalling
*/
public final class Semaphore
{
private volatile boolean state = false;
public synchronized void signal()
{
state = true;
notify();
}
public synchronized void await() throws InterruptedException
{
while( !state ) wait();
state = false;
}
public synchronized boolean await( long timeout ) throws InterruptedException
{
if( !state )
{
wait( timeout );
if( !state ) return false;
}
state = false;
return true;
}
}

View File

@@ -1,7 +1,7 @@
package dan200.computercraft.core.tracking; package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import gnu.trove.map.hash.TObjectLongHashMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -18,13 +18,13 @@ public class ComputerTracker
private long serverCount; private long serverCount;
private long serverTime; private long serverTime;
private final Object2LongOpenHashMap<TrackingField> fields; private final TObjectLongHashMap<TrackingField> fields;
public ComputerTracker( Computer computer ) public ComputerTracker( Computer computer )
{ {
this.computer = new WeakReference<>( computer ); this.computer = new WeakReference<>( computer );
this.computerId = computer.getID(); this.computerId = computer.getID();
this.fields = new Object2LongOpenHashMap<>(); this.fields = new TObjectLongHashMap<>();
} }
ComputerTracker( ComputerTracker timings ) ComputerTracker( ComputerTracker timings )
@@ -39,7 +39,7 @@ public class ComputerTracker
this.serverCount = timings.serverCount; this.serverCount = timings.serverCount;
this.serverTime = timings.serverTime; this.serverTime = timings.serverTime;
this.fields = new Object2LongOpenHashMap<>( timings.fields ); this.fields = new TObjectLongHashMap<>( timings.fields );
} }
@Nullable @Nullable
@@ -90,7 +90,7 @@ public class ComputerTracker
{ {
synchronized( fields ) synchronized( fields )
{ {
fields.addTo( field, change ); fields.adjustOrPutValue( field, change, change );
} }
} }
@@ -106,7 +106,7 @@ public class ComputerTracker
synchronized( fields ) synchronized( fields )
{ {
return fields.getLong( field ); return fields.get( field );
} }
} }

View File

@@ -28,9 +28,6 @@ public class TrackingField
public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes ); public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes ); public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes );
public static final TrackingField COROUTINES_CREATED = TrackingField.of( "coroutines_created", "Coroutines created", x -> String.format( "%4d", x ) );
public static final TrackingField COROUTINES_DISPOSED = TrackingField.of( "coroutines_dead", "Coroutines disposed", x -> String.format( "%4d", x ) );
private final String id; private final String id;
private final String displayName; private final String displayName;
private final LongFunction<String> format; private final LongFunction<String> format;

View File

@@ -8,9 +8,7 @@ package dan200.computercraft.shared.computer.apis;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer; import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block; import net.minecraft.block.Block;
@@ -24,6 +22,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -82,7 +81,7 @@ public class CommandAPI implements ILuaAPI
sender.clearOutput(); sender.clearOutput();
int result = commandManager.executeCommand( sender, command ); int result = commandManager.executeCommand( sender, command );
return new Object[]{ (result > 0), sender.copyOutput() }; return new Object[] { (result > 0), sender.copyOutput() };
} }
catch( Throwable t ) catch( Throwable t )
{ {
@@ -90,7 +89,7 @@ public class CommandAPI implements ILuaAPI
{ {
ComputerCraft.log.error( "Error running command.", t ); ComputerCraft.log.error( "Error running command.", t );
} }
return new Object[]{ false, createOutput( "Java Exception Thrown: " + t.toString() ) }; return new Object[] { false, createOutput( "Java Exception Thrown: " + t.toString() ) };
} }
} }
else else
@@ -131,7 +130,8 @@ public class CommandAPI implements ILuaAPI
} }
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException @Nonnull
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -139,19 +139,19 @@ public class CommandAPI implements ILuaAPI
{ {
// exec // exec
final String command = getString( arguments, 0 ); final String command = getString( arguments, 0 );
return context.executeMainThreadTask( () -> doCommand( command ) ); return MethodResult.onMainThread( () -> MethodResult.of( doCommand( command ) ) );
} }
case 1: case 1:
{ {
// execAsync // execAsync
final String command = getString( arguments, 0 ); final String command = getString( arguments, 0 );
long taskID = context.issueMainThreadTask( () -> doCommand( command ) ); long taskID = context.issueMainThreadTask( () -> doCommand( command ) );
return new Object[] { taskID }; return MethodResult.of( taskID );
} }
case 2: case 2:
{ {
// list // list
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
int i = 1; int i = 1;
Map<Object, Object> result = new HashMap<>(); Map<Object, Object> result = new HashMap<>();
@@ -182,7 +182,7 @@ public class CommandAPI implements ILuaAPI
} }
} }
} }
return new Object[]{ result }; return MethodResult.of( result );
} ); } );
} }
case 3: case 3:
@@ -190,7 +190,7 @@ public class CommandAPI implements ILuaAPI
// getBlockPosition // getBlockPosition
// This is probably safe to do on the Lua thread. Probably. // This is probably safe to do on the Lua thread. Probably.
BlockPos pos = m_computer.getPos(); BlockPos pos = m_computer.getPos();
return new Object[] { pos.getX(), pos.getY(), pos.getZ() }; return MethodResult.of( pos.getX(), pos.getY(), pos.getZ() );
} }
case 4: case 4:
{ {
@@ -201,7 +201,7 @@ public class CommandAPI implements ILuaAPI
final int maxx = getInt( arguments, 3 ); final int maxx = getInt( arguments, 3 );
final int maxy = getInt( arguments, 4 ); final int maxy = getInt( arguments, 4 );
final int maxz = getInt( arguments, 5 ); final int maxz = getInt( arguments, 5 );
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
// Get the details of the block // Get the details of the block
World world = m_computer.getWorld(); World world = m_computer.getWorld();
@@ -236,7 +236,7 @@ public class CommandAPI implements ILuaAPI
} }
} }
} }
return new Object[]{ results }; return MethodResult.of( results );
} ); } );
} }
case 5: case 5:
@@ -245,14 +245,14 @@ public class CommandAPI implements ILuaAPI
final int x = getInt( arguments, 0 ); final int x = getInt( arguments, 0 );
final int y = getInt( arguments, 1 ); final int y = getInt( arguments, 1 );
final int z = getInt( arguments, 2 ); final int z = getInt( arguments, 2 );
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
// Get the details of the block // Get the details of the block
World world = m_computer.getWorld(); World world = m_computer.getWorld();
BlockPos position = new BlockPos( x, y, z ); BlockPos position = new BlockPos( x, y, z );
if( WorldUtil.isBlockInWorld( world, position ) ) if( WorldUtil.isBlockInWorld( world, position ) )
{ {
return new Object[]{ getBlockInfo( world, position ) }; return MethodResult.of( getBlockInfo( world, position ) );
} }
else else
{ {
@@ -262,8 +262,16 @@ public class CommandAPI implements ILuaAPI
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} }

View File

@@ -6,11 +6,15 @@
package dan200.computercraft.shared.computer.blocks; package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ComputerPeripheral public class ComputerPeripheral
implements IPeripheral implements IPeripheral
@@ -23,7 +27,7 @@ public class ComputerPeripheral
m_type = type; m_type = type;
m_computer = computer; m_computer = computer;
} }
// IPeripheral implementation // IPeripheral implementation
@Nonnull @Nonnull
@@ -47,8 +51,9 @@ public class ComputerPeripheral
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{ {
switch( method ) switch( method )
{ {
@@ -56,42 +61,48 @@ public class ComputerPeripheral
{ {
// turnOn // turnOn
m_computer.turnOn(); m_computer.turnOn();
return null; return MethodResult.empty();
} }
case 1: case 1:
{ {
// shutdown // shutdown
m_computer.shutdown(); m_computer.shutdown();
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
// reboot // reboot
m_computer.reboot(); m_computer.reboot();
return null; return MethodResult.empty();
} }
case 3: case 3:
{ {
// getID // getID
return new Object[] { return MethodResult.of( m_computer.assignID() );
m_computer.assignID()
};
} }
case 4: case 4:
{ {
// isOn // isOn
return new Object[] { m_computer.isOn() }; return MethodResult.of( m_computer.isOn() );
} }
case 5: case 5:
// getLabel // getLabel
return new Object[] { m_computer.getLabel() }; return MethodResult.of( m_computer.getLabel() );
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override @Override
public boolean equals( IPeripheral other ) public boolean equals( IPeripheral other )
{ {

View File

@@ -1,23 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.datafix;
import dan200.computercraft.ComputerCraft;
import net.minecraft.util.datafix.FixTypes;
import net.minecraftforge.common.util.CompoundDataFixer;
import net.minecraftforge.common.util.ModFixs;
public class Fixes
{
public static final int VERSION = 1;
public static void register( CompoundDataFixer fixer )
{
ModFixs fixes = fixer.init( ComputerCraft.MOD_ID, VERSION );
fixes.registerFix( FixTypes.BLOCK_ENTITY, new TileEntityDataFixer() );
}
}

View File

@@ -1,39 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.datafix;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.datafix.IFixableData;
import javax.annotation.Nonnull;
import static dan200.computercraft.ComputerCraft.MOD_ID;
import static dan200.computercraft.shared.datafix.Fixes.VERSION;
/**
* Fixes up the botched tile entity IDs from the 1.11 port.
*/
public class TileEntityDataFixer implements IFixableData
{
@Override
public int getFixVersion()
{
return VERSION;
}
@Nonnull
@Override
public NBTTagCompound fixTagCompound( @Nonnull NBTTagCompound tag )
{
String id = tag.getString( "id" );
if( id.startsWith( MOD_ID + " : " ) )
{
tag.setString( "id", id.replaceFirst( MOD_ID + " : ", MOD_ID + ":" ) );
}
return tag;
}
}

View File

@@ -69,6 +69,7 @@ final class BundledCapabilityProvider implements ICapabilityProvider
emitter = emitters[index] = () -> toBytes( tile.getBundledRedstoneOutput( side ) ); emitter = emitters[index] = () -> toBytes( tile.getBundledRedstoneOutput( side ) );
} }
} }
;
return CAPABILITY_EMITTER.cast( emitter ); return CAPABILITY_EMITTER.cast( emitter );
} }

View File

@@ -6,14 +6,17 @@
package dan200.computercraft.shared.peripheral.commandblock; package dan200.computercraft.shared.peripheral.commandblock;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.tileentity.TileEntityCommandBlock; import net.minecraft.tileentity.TileEntityCommandBlock;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
@@ -46,17 +49,18 @@ public class CommandBlockPeripheral implements IPeripheral
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull final Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull final Object[] arguments ) throws LuaException
{ {
switch (method) switch( method )
{ {
case 0: case 0:
{ {
// getCommand // getCommand
return context.executeMainThreadTask( () -> new Object[] { return MethodResult.onMainThread( () ->
m_commandBlock.getCommandBlockLogic().getCommand() MethodResult.of( m_commandBlock.getCommandBlockLogic().getCommand() )
} ); );
} }
case 1: case 1:
{ {
@@ -69,27 +73,35 @@ public class CommandBlockPeripheral implements IPeripheral
m_commandBlock.getWorld().markBlockRangeForRenderUpdate( pos, pos ); m_commandBlock.getWorld().markBlockRangeForRenderUpdate( pos, pos );
return null; return null;
} ); } );
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
// runCommand // runCommand
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
m_commandBlock.getCommandBlockLogic().trigger( m_commandBlock.getWorld() ); m_commandBlock.getCommandBlockLogic().trigger( m_commandBlock.getWorld() );
int result = m_commandBlock.getCommandBlockLogic().getSuccessCount(); int result = m_commandBlock.getCommandBlockLogic().getSuccessCount();
if( result > 0 ) if( result > 0 )
{ {
return new Object[] { true }; return MethodResult.of( true );
} }
else else
{ {
return new Object[] { false, "Command failed" }; return MethodResult.of( false, "Command failed" );
} }
} ); } );
} }
} }
return null; return MethodResult.empty();
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
} }
@Override @Override

View File

@@ -6,8 +6,10 @@
package dan200.computercraft.shared.peripheral.diskdrive; package dan200.computercraft.shared.peripheral.diskdrive;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
@@ -17,6 +19,7 @@ import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.optString; import static dan200.computercraft.core.apis.ArgumentHelper.optString;
@@ -55,17 +58,18 @@ public class DiskDrivePeripheral implements IPeripheral
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
case 0: case 0:
{ {
// isDiskPresent // isDiskPresent
return new Object[] { return MethodResult.of(
!m_diskDrive.getDiskStack().isEmpty() !m_diskDrive.getDiskStack().isEmpty()
}; );
} }
case 1: case 1:
{ {
@@ -73,9 +77,9 @@ public class DiskDrivePeripheral implements IPeripheral
IMedia media = m_diskDrive.getDiskMedia(); IMedia media = m_diskDrive.getDiskMedia();
if( media != null ) if( media != null )
{ {
return new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) }; return MethodResult.of( media.getLabel( m_diskDrive.getDiskStack() ) );
} }
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
@@ -96,21 +100,21 @@ public class DiskDrivePeripheral implements IPeripheral
throw new LuaException( "Disk label cannot be changed" ); throw new LuaException( "Disk label cannot be changed" );
} }
} }
return null; return MethodResult.empty();
} }
case 3: case 3:
{ {
// hasData // hasData
return new Object[] { return MethodResult.of(
m_diskDrive.getDiskMountPath( computer ) != null m_diskDrive.getDiskMountPath( computer ) != null
}; );
} }
case 4: case 4:
{ {
// getMountPath // getMountPath
return new Object[] { return MethodResult.of(
m_diskDrive.getDiskMountPath( computer ) m_diskDrive.getDiskMountPath( computer )
}; );
} }
case 5: case 5:
{ {
@@ -118,9 +122,9 @@ public class DiskDrivePeripheral implements IPeripheral
IMedia media = m_diskDrive.getDiskMedia(); IMedia media = m_diskDrive.getDiskMedia();
if( media != null ) if( media != null )
{ {
return new Object[] { media.getAudio( m_diskDrive.getDiskStack() ) != null }; return MethodResult.of( media.getAudio( m_diskDrive.getDiskStack() ) != null );
} }
return new Object[] { false }; return MethodResult.of( false );
} }
case 6: case 6:
{ {
@@ -128,27 +132,27 @@ public class DiskDrivePeripheral implements IPeripheral
IMedia media = m_diskDrive.getDiskMedia(); IMedia media = m_diskDrive.getDiskMedia();
if( media != null ) if( media != null )
{ {
return new Object[] { media.getAudioTitle( m_diskDrive.getDiskStack() ) }; return MethodResult.of( media.getAudioTitle( m_diskDrive.getDiskStack() ) );
} }
return new Object[] { false }; return MethodResult.of( false );
} }
case 7: case 7:
{ {
// playAudio // playAudio
m_diskDrive.playDiskAudio(); m_diskDrive.playDiskAudio();
return null; return MethodResult.empty();
} }
case 8: case 8:
{ {
// stopAudio // stopAudio
m_diskDrive.stopDiskAudio(); m_diskDrive.stopDiskAudio();
return null; return MethodResult.empty();
} }
case 9: case 9:
{ {
// eject // eject
m_diskDrive.ejectDisk(); m_diskDrive.ejectDisk();
return null; return MethodResult.empty();
} }
case 10: case 10:
{ {
@@ -159,18 +163,27 @@ public class DiskDrivePeripheral implements IPeripheral
Item item = disk.getItem(); Item item = disk.getItem();
if( item instanceof ItemDiskLegacy ) if( item instanceof ItemDiskLegacy )
{ {
return new Object[] { ((ItemDiskLegacy)item).getDiskID( disk ) }; return MethodResult.of( ((ItemDiskLegacy)item).getDiskID( disk ) );
} }
} }
return null; return MethodResult.empty();
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override @Override
public void attach( @Nonnull IComputerAccess computer ) public void attach( @Nonnull IComputerAccess computer )
{ {

View File

@@ -6,51 +6,64 @@
package dan200.computercraft.shared.peripheral.modem; package dan200.computercraft.shared.peripheral.modem;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.network.IPacketNetwork; import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.IPacketReceiver; import dan200.computercraft.api.network.IPacketReceiver;
import dan200.computercraft.api.network.IPacketSender; import dan200.computercraft.api.network.IPacketSender;
import dan200.computercraft.api.network.Packet; import dan200.computercraft.api.network.Packet;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.HashSet; import javax.annotation.Nullable;
import java.util.Set;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt; import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver public abstract class ModemPeripheral
implements IPeripheral, IPacketSender, IPacketReceiver
{ {
private IPacketNetwork m_network; private IPacketNetwork m_network;
private final Set<IComputerAccess> m_computers = new HashSet<>( 1 ); private IComputerAccess m_computer;
private final ModemState m_state; private final TIntSet m_channels;
protected ModemPeripheral( ModemState state ) private boolean m_open;
{ private boolean m_changed;
this.m_state = state;
}
public ModemState getModemState() public ModemPeripheral()
{ {
return m_state; m_network = null;
m_computer = null;
m_channels = new TIntHashSet();
m_open = false;
m_changed = true;
} }
private synchronized void setNetwork( IPacketNetwork network ) private synchronized void setNetwork( IPacketNetwork network )
{ {
if( m_network == network ) return; if( m_network != network )
{
// Leave old network
if( m_network != null )
{
m_network.removeReceiver( this );
}
// Leave old network // Set new network
if( m_network != null ) m_network.removeReceiver( this ); m_network = network;
// Set new network // Join new network
m_network = network; if( m_network != null )
{
// Join new network m_network.addReceiver( this );
if( m_network != null ) m_network.addReceiver( this ); }
}
} }
protected void switchNetwork() protected void switchNetwork()
@@ -58,22 +71,39 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
setNetwork( getNetwork() ); setNetwork( getNetwork() );
} }
public void destroy() public synchronized void destroy()
{ {
setNetwork( null ); setNetwork( null );
m_channels.clear();
m_open = false;
}
public boolean pollChanged()
{
if( m_changed )
{
m_changed = false;
return true;
}
return false;
}
public boolean isActive()
{
return m_computer != null && m_open;
} }
@Override @Override
public void receiveSameDimension( @Nonnull Packet packet, double distance ) public void receiveSameDimension( @Nonnull Packet packet, double distance )
{ {
if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return; if( packet.getSender() == this ) return;
synchronized( m_computers ) synchronized (m_channels)
{ {
for( IComputerAccess computer : m_computers ) if( m_computer != null && m_channels.contains( packet.getChannel() ) )
{ {
computer.queueEvent( "modem_message", new Object[]{ m_computer.queueEvent( "modem_message", new Object[] {
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
} ); } );
} }
} }
@@ -82,21 +112,21 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
@Override @Override
public void receiveDifferentDimension( @Nonnull Packet packet ) public void receiveDifferentDimension( @Nonnull Packet packet )
{ {
if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return; if( packet.getSender() == this ) return;
synchronized( m_computers ) synchronized (m_channels)
{ {
for( IComputerAccess computer : m_computers ) if( m_computer != null && m_channels.contains( packet.getChannel() ) )
{ {
computer.queueEvent( "modem_message", new Object[]{ m_computer.queueEvent( "modem_message", new Object[] {
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload() m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
} ); } );
} }
} }
} }
protected abstract IPacketNetwork getNetwork(); protected abstract IPacketNetwork getNetwork();
// IPeripheral implementation // IPeripheral implementation
@Nonnull @Nonnull
@@ -105,12 +135,12 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
{ {
return "modem"; return "modem";
} }
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()
{ {
return new String[]{ return new String[] {
"open", "open",
"isOpen", "isOpen",
"close", "close",
@@ -119,7 +149,7 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
"isWireless", "isWireless",
}; };
} }
private static int parseChannel( Object[] arguments, int index ) throws LuaException private static int parseChannel( Object[] arguments, int index ) throws LuaException
{ {
int channel = getInt( arguments, index ); int channel = getInt( arguments, index );
@@ -129,9 +159,10 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
} }
return channel; return channel;
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -139,39 +170,81 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
{ {
// open // open
int channel = parseChannel( arguments, 0 ); int channel = parseChannel( arguments, 0 );
m_state.open( channel ); synchronized( this )
return null; {
if( !m_channels.contains( channel ) )
{
if( m_channels.size() >= 128 )
{
throw new LuaException( "Too many open channels" );
}
m_channels.add( channel );
if( !m_open )
{
m_open = true;
m_changed = true;
}
}
}
return MethodResult.empty();
} }
case 1: case 1:
{ {
// isOpen // isOpen
int channel = parseChannel( arguments, 0 ); int channel = parseChannel( arguments, 0 );
return new Object[]{ m_state.isOpen( channel ) }; synchronized( this )
{
boolean open = m_channels.contains( channel );
return MethodResult.of( open );
}
} }
case 2: case 2:
{ {
// close // close
int channel = parseChannel( arguments, 0 ); int channel = parseChannel( arguments, 0 );
m_state.close( channel ); synchronized( this )
return null; {
if( m_channels.remove( channel ) )
{
if( m_channels.size() == 0 )
{
m_open = false;
m_changed = true;
}
}
}
return MethodResult.empty();
} }
case 3: case 3:
{ {
// closeAll // closeAll
m_state.closeAll(); synchronized( this )
return null; {
if( m_channels.size() > 0 )
{
m_channels.clear();
if( m_open )
{
m_open = false;
m_changed = true;
}
}
}
return MethodResult.empty();
} }
case 4: case 4:
{ {
// transmit // transmit
int channel = parseChannel( arguments, 0 ); int channel = parseChannel( arguments, 0 );
int replyChannel = parseChannel( arguments, 1 ); int replyChannel = parseChannel( arguments, 1 );
Object payload = arguments.length > 2 ? arguments[2] : null; Object payload = (arguments.length >= 3) ? arguments[2] : null;
synchronized( this ) synchronized( this )
{ {
World world = getWorld(); World world = getWorld();
Vec3d position = getPosition(); Vec3d position = getPosition();
if( world != null && position != null && m_network != null ) if( world != null && position != null && m_network != null)
{ {
Packet packet = new Packet( channel, replyChannel, payload, this ); Packet packet = new Packet( channel, replyChannel, payload, this );
if( isInterdimensional() ) if( isInterdimensional() )
@@ -184,60 +257,78 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
} }
} }
} }
return null; return MethodResult.empty();
} }
case 5: case 5:
{ {
// isWireless // isWireless
IPacketNetwork network = m_network; synchronized( this )
return new Object[]{ network != null && network.isWireless() }; {
if( m_network != null )
{
return MethodResult.of( m_network.isWireless() );
}
}
return MethodResult.of(false);
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( computer, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override @Override
public synchronized void attach( @Nonnull IComputerAccess computer ) public synchronized void attach( @Nonnull IComputerAccess computer )
{ {
synchronized( m_computers ) m_computer = computer;
{
m_computers.add( computer );
}
setNetwork( getNetwork() ); setNetwork( getNetwork() );
m_open = !m_channels.isEmpty();
} }
@Override @Override
public synchronized void detach( @Nonnull IComputerAccess computer ) public synchronized void detach( @Nonnull IComputerAccess computer )
{ {
boolean empty; if( m_network != null )
synchronized( m_computers )
{ {
m_computers.remove( computer ); m_network.removeReceiver( this );
empty = m_computers.isEmpty(); m_channels.clear();
m_network = null;
} }
if( empty ) setNetwork( null ); m_computer = null;
if( m_open )
{
m_open = false;
m_changed = true;
}
}
public IComputerAccess getComputer()
{
return m_computer;
} }
@Nonnull @Nonnull
@Override @Override
public String getSenderID() public String getSenderID()
{ {
synchronized( m_computers ) if( m_computer == null )
{ {
if( m_computers.size() != 1 ) return "unknown";
{ }
return "unknown"; else
} {
else return m_computer.getID() + "_" + m_computer.getAttachmentName();
{
IComputerAccess computer = m_computers.iterator().next();
return computer.getID() + "_" + computer.getAttachmentName();
}
} }
} }
} }

View File

@@ -1,77 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.modem;
import dan200.computercraft.api.lua.LuaException;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.concurrent.atomic.AtomicBoolean;
public class ModemState
{
private boolean open = false;
private AtomicBoolean changed = new AtomicBoolean( true );
private final IntSet channels = new IntOpenHashSet();
private void setOpen( boolean open )
{
if( this.open == open ) return;
this.open = open;
this.changed.set( true );
}
public boolean pollChanged()
{
return changed.getAndSet( false );
}
public boolean isOpen()
{
return open;
}
public boolean isOpen( int channel )
{
synchronized( channels )
{
return channels.contains( channel );
}
}
public void open( int channel ) throws LuaException
{
synchronized( channels )
{
if( !channels.contains( channel ) )
{
if( channels.size() >= 128 ) throw new LuaException( "Too many open channels" );
channels.add( channel );
setOpen( true );
}
}
}
public void close( int channel )
{
synchronized( channels )
{
channels.remove( channel );
if( channels.isEmpty() ) setOpen( false );
}
}
public void closeAll()
{
synchronized( channels )
{
channels.clear();
setOpen( false );
}
}
}

View File

@@ -25,7 +25,7 @@ public class TileAdvancedModem extends TileModemBase
public Peripheral( TileModemBase entity ) public Peripheral( TileModemBase entity )
{ {
super( new ModemState(), true ); super( true );
m_entity = entity; m_entity = entity;
} }

View File

@@ -97,7 +97,7 @@ public class TileCable extends TileModemBase
private boolean m_hasDirection = false; private boolean m_hasDirection = false;
private boolean m_connectionsFormed = false; private boolean m_connectionsFormed = false;
private WiredModemElement m_cable; private WiredModemElement m_cable;
private IWiredNode m_node; private IWiredNode m_node;
@@ -106,7 +106,7 @@ public class TileCable extends TileModemBase
{ {
m_cable = new CableElement( this ); m_cable = new CableElement( this );
m_node = m_cable.getNode(); m_node = m_cable.getNode();
return new WiredModemPeripheral( new ModemState(), m_cable ) return new WiredModemPeripheral( m_cable )
{ {
@Nonnull @Nonnull
@Override @Override
@@ -453,8 +453,14 @@ public class TileCable extends TileModemBase
protected void updateAnim() protected void updateAnim()
{ {
int anim = 0; int anim = 0;
if( m_modem.getModemState().isOpen() ) anim |= 1; if( m_modem.isActive() )
if( m_peripheralAccessAllowed ) anim |= 2; {
anim += 1;
}
if( m_peripheralAccessAllowed )
{
anim += 2;
}
setAnim( anim ); setAnim( anim );
} }

View File

@@ -56,7 +56,10 @@ public abstract class TileModemBase extends TilePeripheralBase
public void onNeighbourChange() public void onNeighbourChange()
{ {
EnumFacing dir = getDirection(); EnumFacing dir = getDirection();
if( !getWorld().isSideSolid( getPos().offset( dir ), dir.getOpposite() ) ) if( !getWorld().isSideSolid(
getPos().offset( dir ),
dir.getOpposite()
) )
{ {
// Drop everything and remove block // Drop everything and remove block
((BlockGeneric)getBlockType()).dropAllItems( getWorld(), getPos(), false ); ((BlockGeneric)getBlockType()).dropAllItems( getWorld(), getPos(), false );
@@ -76,15 +79,22 @@ public abstract class TileModemBase extends TilePeripheralBase
public void update() public void update()
{ {
super.update(); super.update();
if( !getWorld().isRemote && m_modem.getModemState().pollChanged() ) if( !getWorld().isRemote && m_modem.pollChanged() )
{ {
updateAnim(); updateAnim();
} }
} }
protected void updateAnim() protected void updateAnim()
{ {
setAnim( m_modem.getModemState().isOpen() ? 1 : 0 ); if( m_modem.isActive() )
{
setAnim(1);
}
else
{
setAnim(0);
}
} }
@Override @Override
@@ -99,6 +109,15 @@ public abstract class TileModemBase extends TilePeripheralBase
@Override @Override
public IPeripheral getPeripheral( EnumFacing side ) public IPeripheral getPeripheral( EnumFacing side )
{ {
return side == getDirection() ? m_modem : null; if( side == getDirection() )
{
return m_modem;
}
return null;
}
protected boolean isAttached()
{
return (m_modem != null) && (m_modem.getComputer() != null);
} }
} }

View File

@@ -83,7 +83,6 @@ public class TileWiredModemFull extends TilePeripheralBase
private boolean m_destroyed = false; private boolean m_destroyed = false;
private boolean m_connectionsFormed = false; private boolean m_connectionsFormed = false;
private final ModemState m_modemState = new ModemState();
private final WiredModemElement m_element = new FullElement( this ); private final WiredModemElement m_element = new FullElement( this );
private final IWiredNode m_node = m_element.getNode(); private final IWiredNode m_node = m_element.getNode();
@@ -237,8 +236,19 @@ public class TileWiredModemFull extends TilePeripheralBase
protected void updateAnim() protected void updateAnim()
{ {
int anim = 0; int anim = 0;
if( m_modemState.isOpen() ) anim |= 1; for( WiredModemPeripheral modem : m_modems )
if( m_peripheralAccessAllowed ) anim |= 2; {
if( modem != null && modem.isActive() )
{
anim += 1;
break;
}
}
if( m_peripheralAccessAllowed )
{
anim += 2;
}
setAnim( anim ); setAnim( anim );
} }
@@ -254,7 +264,12 @@ public class TileWiredModemFull extends TilePeripheralBase
{ {
if( !getWorld().isRemote ) if( !getWorld().isRemote )
{ {
if( m_modemState.pollChanged() ) updateAnim(); boolean changed = false;
for( WiredModemPeripheral peripheral : m_modems )
{
if( peripheral != null && peripheral.pollChanged() ) changed = true;
}
if( changed ) updateAnim();
if( !m_connectionsFormed ) if( !m_connectionsFormed )
{ {
@@ -387,7 +402,7 @@ public class TileWiredModemFull extends TilePeripheralBase
if( peripheral == null ) if( peripheral == null )
{ {
WiredModemLocalPeripheral localPeripheral = m_peripherals[side.ordinal()]; WiredModemLocalPeripheral localPeripheral = m_peripherals[side.ordinal()];
peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_modemState, m_element ) peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_element )
{ {
@Nonnull @Nonnull
@Override @Override

View File

@@ -29,7 +29,7 @@ public class TileWirelessModem extends TileModemBase
public Peripheral( TileModemBase entity ) public Peripheral( TileModemBase entity )
{ {
super( new ModemState(), false ); super( false );
m_entity = entity; m_entity = entity;
} }

View File

@@ -3,8 +3,9 @@ package dan200.computercraft.shared.peripheral.modem;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.network.IPacketNetwork; import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender; import dan200.computercraft.api.network.wired.IWiredSender;
@@ -18,8 +19,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
@@ -27,11 +26,10 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
{ {
private final WiredModemElement modem; private final WiredModemElement modem;
private final Map<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> peripheralWrappers = new HashMap<>( 1 ); private final Map<String, RemotePeripheralWrapper> peripheralWrappers = new HashMap<>();
public WiredModemPeripheral( ModemState state, WiredModemElement modem ) public WiredModemPeripheral( WiredModemElement modem )
{ {
super( state );
this.modem = modem; this.modem = modem;
} }
@@ -82,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
return newMethods; return newMethods;
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
String[] methods = super.getMethodNames(); String[] methods = super.getMethodNames();
switch( method - methods.length ) switch( method - methods.length )
@@ -91,60 +90,62 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
case 0: case 0:
{ {
// getNamesRemote // getNamesRemote
Map<String, RemotePeripheralWrapper> wrappers = getWrappers( computer ); synchronized( peripheralWrappers )
Map<Object, Object> table = new HashMap<>();
if( wrappers != null )
{ {
int idx = 1; int idx = 1;
for( String name : wrappers.keySet() ) table.put( idx++, name ); Map<Object, Object> table = new HashMap<>();
for( String name : peripheralWrappers.keySet() )
{
table.put( idx++, name );
}
return MethodResult.of( table );
} }
return new Object[]{ table };
} }
case 1: case 1:
{ {
// isPresentRemote // isPresentRemote
String name = getString( arguments, 0 ); String type = getTypeRemote( getString( arguments, 0 ) );
return new Object[]{ getWrapper( computer, name ) != null }; return MethodResult.of( type != null );
} }
case 2: case 2:
{ {
// getTypeRemote // getTypeRemote
String name = getString( arguments, 0 ); String type = getTypeRemote( getString( arguments, 0 ) );
RemotePeripheralWrapper wrapper = getWrapper( computer, name ); if( type != null )
return wrapper != null ? new Object[]{ wrapper.getType() } : null; {
return MethodResult.of( type );
}
return MethodResult.empty();
} }
case 3: case 3:
{ {
// getMethodsRemote // getMethodsRemote
String name = getString( arguments, 0 ); String[] methodNames = getMethodNamesRemote( getString( arguments, 0 ) );
RemotePeripheralWrapper wrapper = getWrapper( computer, name ); if( methodNames != null )
if( wrapper == null ) return null;
String[] methodNames = wrapper.getMethodNames();
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < methodNames.length; ++i )
{ {
table.put( i + 1, methodNames[i] ); Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < methodNames.length; ++i )
{
table.put( i + 1, methodNames[i] );
}
return MethodResult.of( table );
} }
return new Object[]{ table }; return MethodResult.empty();
} }
case 4: case 4:
{ {
// callRemote // callRemote
String remoteName = getString( arguments, 0 ); String remoteName = getString( arguments, 0 );
String methodName = getString( arguments, 1 ); String methodName = getString( arguments, 1 );
RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName );
if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName );
Object[] methodArgs = new Object[arguments.length - 2]; Object[] methodArgs = new Object[arguments.length - 2];
System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 ); System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 );
return wrapper.callMethod( context, methodName, methodArgs ); return callMethodRemote( remoteName, context, methodName, methodArgs );
} }
case 5: case 5:
{ {
// getNameLocal // getNameLocal
String local = getLocalPeripheral().getConnectedName(); String local = getLocalPeripheral().getConnectedName();
return local == null ? null : new Object[]{ local }; return local == null ? MethodResult.empty() : MethodResult.of( local );
} }
default: default:
{ {
@@ -158,37 +159,29 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
public void attach( @Nonnull IComputerAccess computer ) public void attach( @Nonnull IComputerAccess computer )
{ {
super.attach( computer ); super.attach( computer );
ConcurrentMap<String, RemotePeripheralWrapper> wrappers;
synchronized( peripheralWrappers )
{
wrappers = peripheralWrappers.get( computer );
if( wrappers == null ) peripheralWrappers.put( computer, wrappers = new ConcurrentHashMap<>() );
}
synchronized( modem.getRemotePeripherals() ) synchronized( modem.getRemotePeripherals() )
{ {
for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() ) synchronized( peripheralWrappers )
{ {
attachPeripheralImpl( computer, wrappers, entry.getKey(), entry.getValue() ); for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() )
{
attachPeripheralImpl( entry.getKey(), entry.getValue() );
}
} }
} }
} }
@Override @Override
public void detach( @Nonnull IComputerAccess computer ) public synchronized void detach( @Nonnull IComputerAccess computer )
{ {
Map<String, RemotePeripheralWrapper> wrappers;
synchronized( peripheralWrappers ) synchronized( peripheralWrappers )
{ {
wrappers = peripheralWrappers.remove( computer ); for( RemotePeripheralWrapper wrapper : peripheralWrappers.values() )
{
wrapper.detach();
}
peripheralWrappers.clear();
} }
if( wrappers != null )
{
for( RemotePeripheralWrapper wrapper : wrappers.values() ) wrapper.detach();
wrappers.clear();
}
super.detach( computer ); super.detach( computer );
} }
@@ -213,12 +206,11 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
public void attachPeripheral( String name, IPeripheral peripheral ) public void attachPeripheral( String name, IPeripheral peripheral )
{ {
if( getComputer() == null ) return;
synchronized( peripheralWrappers ) synchronized( peripheralWrappers )
{ {
for( Map.Entry<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> entry : peripheralWrappers.entrySet() ) attachPeripheralImpl( name, peripheral );
{
attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral );
}
} }
} }
@@ -226,35 +218,63 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
{ {
synchronized( peripheralWrappers ) synchronized( peripheralWrappers )
{ {
for(ConcurrentMap<String, RemotePeripheralWrapper> wrappers : peripheralWrappers.values()) { RemotePeripheralWrapper wrapper = peripheralWrappers.get( name );
RemotePeripheralWrapper wrapper = wrappers.remove( name ); if( wrapper != null )
if( wrapper != null ) wrapper.detach(); {
peripheralWrappers.remove( name );
wrapper.detach();
} }
} }
} }
private void attachPeripheralImpl( IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral ) private void attachPeripheralImpl( String periphName, IPeripheral peripheral )
{ {
if( !peripherals.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) ) if( !peripheralWrappers.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) )
{ {
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, computer, periphName ); RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
peripherals.put( periphName, wrapper ); peripheralWrappers.put( periphName, wrapper );
wrapper.attach(); wrapper.attach();
} }
} }
private ConcurrentMap<String, RemotePeripheralWrapper> getWrappers( IComputerAccess computer ) { private String getTypeRemote( String remoteName )
{
synchronized( peripheralWrappers ) synchronized( peripheralWrappers )
{ {
return peripheralWrappers.get( computer ); RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getType();
}
} }
return null;
} }
private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String remoteName ) private String[] getMethodNamesRemote( String remoteName )
{ {
ConcurrentMap<String, RemotePeripheralWrapper> wrappers = getWrappers( computer ); synchronized( peripheralWrappers )
return wrappers == null ? null : wrappers.get( remoteName ); {
RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getMethodNames();
}
}
return null;
}
private MethodResult callMethodRemote( String remoteName, ICallContext context, String method, Object[] arguments ) throws LuaException
{
RemotePeripheralWrapper wrapper;
synchronized( peripheralWrappers )
{
wrapper = peripheralWrappers.get( remoteName );
}
if( wrapper != null )
{
return wrapper.callMethod( context, method, arguments );
}
throw new LuaException( "No peripheral: " + remoteName );
} }
private static class RemotePeripheralWrapper implements IComputerAccess, IComputerOwned private static class RemotePeripheralWrapper implements IComputerAccess, IComputerOwned
@@ -312,7 +332,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
return m_methods; return m_methods;
} }
public Object[] callMethod( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( ICallContext context, String methodName, Object[] arguments ) throws LuaException
{ {
if( m_methodMap.containsKey( methodName ) ) if( m_methodMap.containsKey( methodName ) )
{ {

View File

@@ -15,16 +15,9 @@ public abstract class WirelessModemPeripheral extends ModemPeripheral
{ {
private boolean m_advanced; private boolean m_advanced;
public WirelessModemPeripheral( ModemState state, boolean advanced )
{
super( state );
m_advanced = advanced;
}
@Deprecated
public WirelessModemPeripheral( boolean advanced ) public WirelessModemPeripheral( boolean advanced )
{ {
this( new ModemState(), advanced ); m_advanced = advanced;
} }
@Override @Override

View File

@@ -13,9 +13,8 @@ import dan200.computercraft.api.network.IPacketSender;
import dan200.computercraft.api.network.Packet; import dan200.computercraft.api.network.Packet;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Collections; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class WirelessNetwork implements IPacketNetwork public class WirelessNetwork implements IPacketNetwork
{ {
@@ -35,34 +34,45 @@ public class WirelessNetwork implements IPacketNetwork
s_universalNetwork = null; s_universalNetwork = null;
} }
private final Set<IPacketReceiver> m_receivers = Collections.newSetFromMap( new ConcurrentHashMap<>() ); private final Set<IPacketReceiver> m_receivers;
private WirelessNetwork()
{
m_receivers = new HashSet<>();
}
@Override @Override
public void addReceiver( @Nonnull IPacketReceiver receiver ) public synchronized void addReceiver( @Nonnull IPacketReceiver receiver )
{ {
Preconditions.checkNotNull( receiver, "device cannot be null" ); Preconditions.checkNotNull( receiver, "device cannot be null" );
m_receivers.add( receiver ); m_receivers.add( receiver );
} }
@Override @Override
public void removeReceiver( @Nonnull IPacketReceiver receiver ) public synchronized void removeReceiver( @Nonnull IPacketReceiver receiver )
{ {
Preconditions.checkNotNull( receiver, "device cannot be null" ); Preconditions.checkNotNull( receiver, "device cannot be null" );
m_receivers.remove( receiver ); m_receivers.remove( receiver );
} }
@Override @Override
public void transmitSameDimension( @Nonnull Packet packet, double range ) public synchronized void transmitSameDimension( @Nonnull Packet packet, double range )
{ {
Preconditions.checkNotNull( packet, "packet cannot be null" ); Preconditions.checkNotNull( packet, "packet cannot be null" );
for( IPacketReceiver device : m_receivers ) tryTransmit( device, packet, range, false ); for( IPacketReceiver device : m_receivers )
{
tryTransmit( device, packet, range, false );
}
} }
@Override @Override
public void transmitInterdimensional( @Nonnull Packet packet ) public synchronized void transmitInterdimensional( @Nonnull Packet packet )
{ {
Preconditions.checkNotNull( packet, "packet cannot be null" ); Preconditions.checkNotNull( packet, "packet cannot be null" );
for( IPacketReceiver device : m_receivers ) tryTransmit( device, packet, 0, true ); for (IPacketReceiver device : m_receivers)
{
tryTransmit( device, packet, 0, true );
}
} }
private void tryTransmit( IPacketReceiver receiver, Packet packet, double range, boolean interdimensional ) private void tryTransmit( IPacketReceiver receiver, Packet packet, double range, boolean interdimensional )

View File

@@ -6,8 +6,10 @@
package dan200.computercraft.shared.peripheral.monitor; package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.TermAPI; import dan200.computercraft.core.apis.TermAPI;
@@ -16,6 +18,7 @@ import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.*; import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -66,13 +69,13 @@ public class MonitorPeripheral implements IPeripheral
"setPaletteColor", "setPaletteColor",
"getPaletteColour", "getPaletteColour",
"getPaletteColor", "getPaletteColor",
"getTextScale", "getTextScale"
"getCursorBlink",
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object args[] ) throws LuaException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object args[] ) throws LuaException
{ {
ServerMonitor monitor = m_monitor.getCachedServerMonitor(); ServerMonitor monitor = m_monitor.getCachedServerMonitor();
if( monitor == null ) throw new LuaException( "Monitor has been detatched" ); if( monitor == null ) throw new LuaException( "Monitor has been detatched" );
@@ -86,21 +89,24 @@ public class MonitorPeripheral implements IPeripheral
{ {
// write // write
String text; String text;
if( args.length > 0 && args[0] != null ) { if( args.length > 0 && args[ 0 ] != null )
text = args[0].toString(); {
} else { text = args[ 0 ].toString();
}
else
{
text = ""; text = "";
} }
terminal.write( text ); terminal.write( text );
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
return null; return MethodResult.empty();
} }
case 1: case 1:
{ {
// scroll // scroll
int value = getInt( args, 0 ); int value = getInt( args, 0 );
terminal.scroll( value ); terminal.scroll( value );
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
@@ -108,42 +114,42 @@ public class MonitorPeripheral implements IPeripheral
int x = getInt( args, 0 ) - 1; int x = getInt( args, 0 ) - 1;
int y = getInt( args, 1 ) - 1; int y = getInt( args, 1 ) - 1;
terminal.setCursorPos( x, y ); terminal.setCursorPos( x, y );
return null; return MethodResult.empty();
} }
case 3: case 3:
{ {
// setCursorBlink // setCursorBlink
boolean blink = getBoolean( args, 0 ); boolean blink = getBoolean( args, 0 );
terminal.setCursorBlink( blink ); terminal.setCursorBlink( blink );
return null; return MethodResult.empty();
} }
case 4: case 4:
{ {
// getCursorPos // getCursorPos
return new Object[] { return MethodResult.of(
terminal.getCursorX() + 1, terminal.getCursorX() + 1,
terminal.getCursorY() + 1 terminal.getCursorY() + 1
}; );
} }
case 5: case 5:
{ {
// getSize // getSize
return new Object[] { return MethodResult.of(
terminal.getWidth(), terminal.getWidth(),
terminal.getHeight() terminal.getHeight()
}; );
} }
case 6: case 6:
{ {
// clear // clear
terminal.clear(); terminal.clear();
return null; return MethodResult.empty();
} }
case 7: case 7:
{ {
// clearLine // clearLine
terminal.clearLine(); terminal.clearLine();
return null; return MethodResult.empty();
} }
case 8: case 8:
{ {
@@ -154,7 +160,7 @@ public class MonitorPeripheral implements IPeripheral
throw new LuaException( "Expected number in range 0.5-5" ); throw new LuaException( "Expected number in range 0.5-5" );
} }
monitor.setTextScale( scale ); monitor.setTextScale( scale );
return null; return MethodResult.empty();
} }
case 9: case 9:
case 10: case 10:
@@ -162,7 +168,7 @@ public class MonitorPeripheral implements IPeripheral
// setTextColour/setTextColor // setTextColour/setTextColor
int colour = TermAPI.parseColour( args ); int colour = TermAPI.parseColour( args );
terminal.setTextColour( colour ); terminal.setTextColour( colour );
return null; return MethodResult.empty();
} }
case 11: case 11:
case 12: case 12:
@@ -170,15 +176,15 @@ public class MonitorPeripheral implements IPeripheral
// setBackgroundColour/setBackgroundColor // setBackgroundColour/setBackgroundColor
int colour = TermAPI.parseColour( args ); int colour = TermAPI.parseColour( args );
terminal.setBackgroundColour( colour ); terminal.setBackgroundColour( colour );
return null; return MethodResult.empty();
} }
case 13: case 13:
case 14: case 14:
{ {
// isColour/isColor // isColour/isColor
return new Object[] { return MethodResult.of(
monitor.isColour() monitor.isColour()
}; );
} }
case 15: case 15:
case 16: case 16:
@@ -205,7 +211,7 @@ public class MonitorPeripheral implements IPeripheral
terminal.blit( text, textColour, backgroundColour ); terminal.blit( text, textColour, backgroundColour );
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
return null; return MethodResult.empty();
} }
case 20: case 20:
case 21: case 21:
@@ -225,7 +231,7 @@ public class MonitorPeripheral implements IPeripheral
double b = getReal( args, 3 ); double b = getReal( args, 3 );
TermAPI.setColour( terminal, colour, r, g, b ); TermAPI.setColour( terminal, colour, r, g, b );
} }
return null; return MethodResult.empty();
} }
case 22: case 22:
case 23: case 23:
@@ -237,21 +243,25 @@ public class MonitorPeripheral implements IPeripheral
if( palette != null ) if( palette != null )
{ {
return ArrayUtils.toObject( palette.getColour( colour ) ); return MethodResult.of( (Object[]) ArrayUtils.toObject( palette.getColour( colour ) ) );
} }
return null; return MethodResult.empty();
} }
case 24: case 24:
{ {
// getTextScale // getTextScale
return new Object[] { monitor.getTextScale() / 2.0 }; return MethodResult.of( monitor.getTextScale() / 2.0 );
} }
case 25:
// getCursorBlink
return new Object[] { terminal.getCursorBlink() };
default:
return null;
} }
return MethodResult.empty();
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
} }
@Override @Override
@@ -271,7 +281,7 @@ public class MonitorPeripheral implements IPeripheral
{ {
if( other != null && other instanceof MonitorPeripheral ) if( other != null && other instanceof MonitorPeripheral )
{ {
MonitorPeripheral otherMonitor = (MonitorPeripheral)other; MonitorPeripheral otherMonitor = (MonitorPeripheral) other;
if( otherMonitor.m_monitor == this.m_monitor ) if( otherMonitor.m_monitor == this.m_monitor )
{ {
return true; return true;

View File

@@ -6,13 +6,16 @@
package dan200.computercraft.shared.peripheral.printer; package dan200.computercraft.shared.peripheral.printer;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt; import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
import static dan200.computercraft.core.apis.ArgumentHelper.optString; import static dan200.computercraft.core.apis.ArgumentHelper.optString;
@@ -50,8 +53,9 @@ public class PrinterPeripheral implements IPeripheral
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -68,7 +72,7 @@ public class PrinterPeripheral implements IPeripheral
Terminal page = getCurrentPage(); Terminal page = getCurrentPage();
page.write( text ); page.write( text );
page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() ); page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() );
return null; return MethodResult.empty();
} }
case 1: case 1:
{ {
@@ -77,7 +81,7 @@ public class PrinterPeripheral implements IPeripheral
int y = getInt( args, 1 ) - 1; int y = getInt( args, 1 ) - 1;
Terminal page = getCurrentPage(); Terminal page = getCurrentPage();
page.setCursorPos( x, y ); page.setCursorPos( x, y );
return null; return MethodResult.empty();
} }
case 2: case 2:
{ {
@@ -85,7 +89,7 @@ public class PrinterPeripheral implements IPeripheral
Terminal page = getCurrentPage(); Terminal page = getCurrentPage();
int x = page.getCursorX(); int x = page.getCursorX();
int y = page.getCursorY(); int y = page.getCursorY();
return new Object[] { x + 1, y + 1 }; return MethodResult.of( x + 1, y + 1 );
} }
case 3: case 3:
{ {
@@ -93,23 +97,23 @@ public class PrinterPeripheral implements IPeripheral
Terminal page = getCurrentPage(); Terminal page = getCurrentPage();
int width = page.getWidth(); int width = page.getWidth();
int height = page.getHeight(); int height = page.getHeight();
return new Object[] { width, height }; return MethodResult.of( width, height );
} }
case 4: case 4:
{ {
// newPage // newPage
return new Object[] { m_printer.startNewPage() }; return MethodResult.of( m_printer.startNewPage() );
} }
case 5: case 5:
{ {
// endPage // endPage
getCurrentPage(); getCurrentPage();
return new Object[] { m_printer.endCurrentPage() }; return MethodResult.of( m_printer.endCurrentPage() );
} }
case 6: case 6:
{ {
// getInkLevel // getInkLevel
return new Object[] { m_printer.getInkLevel() }; return MethodResult.of( m_printer.getInkLevel() );
} }
case 7: case 7:
{ {
@@ -117,20 +121,28 @@ public class PrinterPeripheral implements IPeripheral
String title = optString( args, 0, "" ); String title = optString( args, 0, "" );
getCurrentPage(); getCurrentPage();
m_printer.setPageTitle( title ); m_printer.setPageTitle( title );
return null; return MethodResult.empty();
} }
case 8: case 8:
{ {
// getPaperLevel // getPaperLevel
return new Object[] { m_printer.getPaperLevel() }; return MethodResult.of( m_printer.getPaperLevel() );
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override @Override
public boolean equals( IPeripheral other ) public boolean equals( IPeripheral other )
{ {

View File

@@ -7,12 +7,9 @@
package dan200.computercraft.shared.peripheral.speaker; package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.network.play.server.SPacketCustomSound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory; import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
@@ -26,23 +23,22 @@ import java.util.concurrent.atomic.AtomicInteger;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
import static dan200.computercraft.core.apis.ArgumentHelper.optReal; import static dan200.computercraft.core.apis.ArgumentHelper.optReal;
public class SpeakerPeripheral implements IPeripheral public class SpeakerPeripheral implements IPeripheral {
{ private TileSpeaker m_speaker;
private final TileSpeaker m_speaker;
private long m_clock; private long m_clock;
private long m_lastPlayTime; private long m_lastPlayTime;
private final AtomicInteger m_notesThisTick; private final AtomicInteger m_notesThisTick;
public SpeakerPeripheral() public SpeakerPeripheral()
{
this( null );
}
SpeakerPeripheral( TileSpeaker speaker )
{ {
m_clock = 0; m_clock = 0;
m_lastPlayTime = 0; m_lastPlayTime = 0;
m_notesThisTick = new AtomicInteger( ); m_notesThisTick = new AtomicInteger( );
}
SpeakerPeripheral(TileSpeaker speaker)
{
this();
m_speaker = speaker; m_speaker = speaker;
} }
@@ -72,9 +68,15 @@ public class SpeakerPeripheral implements IPeripheral
@Override @Override
public boolean equals( IPeripheral other ) public boolean equals( IPeripheral other )
{ {
if( other == this ) return true; if( other != null && other instanceof SpeakerPeripheral )
if( !(other instanceof SpeakerPeripheral) ) return false; {
return m_speaker == ((SpeakerPeripheral) other).m_speaker; SpeakerPeripheral otherSpeaker = (SpeakerPeripheral) other;
return otherSpeaker.m_speaker == m_speaker;
}
else
{
return false;
}
} }
@Nonnull @Nonnull
@@ -89,90 +91,116 @@ public class SpeakerPeripheral implements IPeripheral
public String[] getMethodNames() public String[] getMethodNames()
{ {
return new String[] { return new String[] {
"playSound", // Plays sound at resourceLocator "playSound", // Plays sound at resourceLocator
"playNote" // Plays note "playNote" // Plays note
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computerAccess, @Nonnull ILuaContext context, int methodIndex, @Nonnull Object[] args ) throws LuaException public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int methodIndex, @Nonnull Object[] args ) throws LuaException
{ {
switch( methodIndex ) switch( methodIndex )
{ {
// playSound // playSound
case 0: case 0:
{ {
String name = getString( args, 0 ); return MethodResult.of( playSound( args, context, false ) );
float volume = (float) optReal( args, 1, 1.0 );
float pitch = (float) optReal( args, 2, 1.0 );
return new Object[] { playSound( context, name, volume, pitch, false ) };
} }
// playNote // playNote
case 1: case 1:
{ {
return playNote( args, context ); return MethodResult.of( playNote( args, context ) );
} }
default: default:
{ {
throw new LuaException( "Method index out of range!" ); throw new LuaException("Method index out of range!");
} }
} }
} }
@Nonnull @Nullable
private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException @Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{ {
String name = getString( arguments, 0 ); return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
private synchronized boolean playNote( Object[] arguments, ICallContext context ) throws LuaException
{
String name = getString(arguments, 0);
float volume = (float) optReal( arguments, 1, 1.0 ); float volume = (float) optReal( arguments, 1, 1.0 );
float pitch = (float) optReal( arguments, 2, 1.0 ); float pitch = (float) optReal( arguments, 2, 1.0 );
String noteName = "block.note." + name;
// Check if the note exists // Check if sound exists
if( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( noteName ) ) ) if ( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( "block.note." + name ) ) )
{ {
throw new LuaException( "Invalid instrument, \"" + name + "\"!" ); throw new LuaException("Invalid instrument, \"" + arguments[0] + "\"!");
} }
// If the resource location for note block notes changes, this method call will need to be updated // If the resource location for note block notes changes, this method call will need to be updated
boolean success = playSound( context, noteName, volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true ); boolean success = playSound(
new Object[] {
"block.note." + name,
(double)Math.min( volume, 3f ),
Math.pow( 2.0f, ( pitch - 12.0f ) / 12.0f)
}, context, true
);
if( success ) m_notesThisTick.incrementAndGet(); if( success )
return new Object[] { success };
}
private synchronized boolean playSound( ILuaContext context, String name, float volume, float pitch, boolean isNote ) throws LuaException
{
if( m_clock - m_lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS &&
(!isNote || m_clock - m_lastPlayTime != 0 || m_notesThisTick.get() >= ComputerCraft.maxNotesPerTick) )
{ {
// Rate limiting occurs when we've already played a sound within the last tick, or we've m_notesThisTick.incrementAndGet();
// played more notes than allowable within the current tick.
return false;
} }
World world = getWorld(); return success;
BlockPos pos = getPos(); }
context.issueMainThreadTask( () -> { private synchronized boolean playSound( Object[] arguments, ICallContext context, boolean isNote ) throws LuaException
MinecraftServer server = world.getMinecraftServer(); {
if( server == null ) return null; String name = getString(arguments, 0);
float volume = (float) optReal( arguments, 1, 1.0 );
float pitch = (float) optReal( arguments, 2, 1.0 );
double x = pos.getX() + 0.5, y = pos.getY() + 0.5, z = pos.getZ() + 0.5; ResourceLocation resourceName = new ResourceLocation( name );
float adjVolume = Math.min( volume, 3.0f );
server.getPlayerList().sendToAllNearExcept(
null, x, y, z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.provider.getDimension(),
new SPacketCustomSound( name, SoundCategory.RECORDS, x, y, z, adjVolume, pitch )
);
return null;
} );
m_lastPlayTime = m_clock; if( m_clock - m_lastPlayTime >= TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS || (isNote && m_clock - m_lastPlayTime == 0 && m_notesThisTick.get() < ComputerCraft.maxNotesPerTick) )
return true; {
if( SoundEvent.REGISTRY.containsKey(resourceName) )
{
final World world = getWorld();
final BlockPos pos = getPos();
final ResourceLocation resource = resourceName;
final float vol = volume;
final float soundPitch = pitch;
context.issueMainThreadTask(new ILuaTask()
{
@Nullable
@Override
public Object[] execute()
{
world.playSound( null, pos, SoundEvent.REGISTRY.getObject( resource ), SoundCategory.RECORDS, Math.min( vol, 3f ), soundPitch );
return null;
}
});
m_lastPlayTime = m_clock;
return true; // Success, return true
}
else
{
return false; // Failed - sound not existent, return false
}
}
else
{
return false; // Failed - rate limited, return false
}
} }
} }

View File

@@ -7,9 +7,7 @@
package dan200.computercraft.shared.pocket.apis; package dan200.computercraft.shared.pocket.apis;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.shared.pocket.core.PocketServerComputer; import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.InventoryUtil;
@@ -21,6 +19,7 @@ import net.minecraft.util.NonNullList;
import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; import net.minecraftforge.items.wrapper.PlayerMainInvWrapper;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class PocketAPI implements ILuaAPI public class PocketAPI implements ILuaAPI
{ {
@@ -49,14 +48,15 @@ public class PocketAPI implements ILuaAPI
}; };
} }
@Nonnull
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
case 0: case 0:
// equipBack // equipBack
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
if( !(m_computer.getEntity() instanceof EntityPlayer) ) if( !(m_computer.getEntity() instanceof EntityPlayer) )
{ {
@@ -94,12 +94,12 @@ public class PocketAPI implements ILuaAPI
// Set the new upgrade // Set the new upgrade
m_computer.setUpgrade( newUpgrade ); m_computer.setUpgrade( newUpgrade );
return null; return MethodResult.empty();
} ); } );
case 1: case 1:
// unequipBack // unequipBack
return context.executeMainThreadTask( () -> return MethodResult.onMainThread( () ->
{ {
if( !(m_computer.getEntity() instanceof EntityPlayer) ) if( !(m_computer.getEntity() instanceof EntityPlayer) )
{ {
@@ -125,13 +125,21 @@ public class PocketAPI implements ILuaAPI
} }
} }
return null; return MethodResult.empty();
} ); } );
default: default:
return null; return MethodResult.empty();
} }
} }
@Override
@Nullable
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
private static IPocketUpgrade findUpgrade( NonNullList<ItemStack> inv, int start, IPocketUpgrade previous ) private static IPocketUpgrade findUpgrade( NonNullList<ItemStack> inv, int start, IPocketUpgrade previous )
{ {
for( int i = 0; i < inv.size(); i++ ) for( int i = 0; i < inv.size(); i++ )

View File

@@ -5,7 +5,6 @@ import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.shared.peripheral.PeripheralType; import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory; import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@@ -76,8 +75,7 @@ public class PocketModem implements IPocketUpgrade
modem.setLocation( entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ ); modem.setLocation( entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ );
} }
ModemState state = modem.getModemState(); access.setLight( modem.isActive() ? 0xBA0000 : -1 );
if( state.pollChanged() ) access.setLight( state.isOpen() ? 0xBA0000 : -1 );
} }
} }
} }

View File

@@ -6,7 +6,6 @@
package dan200.computercraft.shared.pocket.peripherals; package dan200.computercraft.shared.pocket.peripherals;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral; import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
@@ -20,7 +19,7 @@ public class PocketModemPeripheral extends WirelessModemPeripheral
public PocketModemPeripheral( boolean advanced ) public PocketModemPeripheral( boolean advanced )
{ {
super( new ModemState(), advanced ); super( advanced );
m_world = null; m_world = null;
m_position = new Vec3d( 0.0, 0.0, 0.0 ); m_position = new Vec3d( 0.0, 0.0, 0.0 );
} }

View File

@@ -469,9 +469,9 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
private void registerTileEntities() private void registerTileEntities()
{ {
// TileEntities // TileEntities
GameRegistry.registerTileEntity( TileTurtle.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ) ); GameRegistry.registerTileEntity( TileTurtle.class, ComputerCraft.LOWER_ID + " : " + "turtle" );
GameRegistry.registerTileEntity( TileTurtleExpanded.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtleex" ) ); GameRegistry.registerTileEntity( TileTurtleExpanded.class, ComputerCraft.LOWER_ID + " : " + "turtleex" );
GameRegistry.registerTileEntity( TileTurtleAdvanced.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtleadv" ) ); GameRegistry.registerTileEntity( TileTurtleAdvanced.class, ComputerCraft.LOWER_ID + " : " + "turtleadv" );
} }
private void registerForgeHandlers() private void registerForgeHandlers()

View File

@@ -24,7 +24,6 @@ import dan200.computercraft.shared.computer.core.*;
import dan200.computercraft.shared.computer.inventory.ContainerComputer; import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.items.ItemCommandComputer; import dan200.computercraft.shared.computer.items.ItemCommandComputer;
import dan200.computercraft.shared.computer.items.ItemComputer; import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.datafix.Fixes;
import dan200.computercraft.shared.integration.charset.IntegrationCharset; import dan200.computercraft.shared.integration.charset.IntegrationCharset;
import dan200.computercraft.shared.media.common.DefaultMediaProvider; import dan200.computercraft.shared.media.common.DefaultMediaProvider;
import dan200.computercraft.shared.media.inventory.ContainerHeldItem; import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
@@ -78,7 +77,6 @@ import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent; import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent; import net.minecraftforge.fml.client.event.ConfigChangedEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -123,7 +121,6 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
registerTileEntities(); registerTileEntities();
registerForgeHandlers(); registerForgeHandlers();
Fixes.register( FMLCommonHandler.instance().getDataFixer() );
if( Loader.isModLoaded( ModCharset.MODID ) ) IntegrationCharset.register(); if( Loader.isModLoaded( ModCharset.MODID ) ) IntegrationCharset.register();
} }
@@ -481,16 +478,16 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
private void registerTileEntities() private void registerTileEntities()
{ {
// Tile Entities // Tile Entities
GameRegistry.registerTileEntity( TileComputer.class, new ResourceLocation( ComputerCraft.MOD_ID, "computer" ) ); GameRegistry.registerTileEntity( TileComputer.class, ComputerCraft.LOWER_ID + " : " + "computer" );
GameRegistry.registerTileEntity( TileDiskDrive.class, new ResourceLocation( ComputerCraft.MOD_ID, "diskdrive" ) ); GameRegistry.registerTileEntity( TileDiskDrive.class, ComputerCraft.LOWER_ID + " : " + "diskdrive" );
GameRegistry.registerTileEntity( TileWirelessModem.class, new ResourceLocation( ComputerCraft.MOD_ID, "wirelessmodem" ) ); GameRegistry.registerTileEntity( TileWirelessModem.class, ComputerCraft.LOWER_ID + " : " + "wirelessmodem" );
GameRegistry.registerTileEntity( TileMonitor.class, new ResourceLocation( ComputerCraft.MOD_ID, "monitor" ) ); GameRegistry.registerTileEntity( TileMonitor.class, ComputerCraft.LOWER_ID + " : " + "monitor" );
GameRegistry.registerTileEntity( TilePrinter.class, new ResourceLocation( ComputerCraft.MOD_ID, "ccprinter" ) ); GameRegistry.registerTileEntity( TilePrinter.class, ComputerCraft.LOWER_ID + " : " + "ccprinter" );
GameRegistry.registerTileEntity( TileCable.class, new ResourceLocation( ComputerCraft.MOD_ID, "wiredmodem" ) ); GameRegistry.registerTileEntity( TileCable.class, ComputerCraft.LOWER_ID + " : " + "wiredmodem" );
GameRegistry.registerTileEntity( TileCommandComputer.class, new ResourceLocation( ComputerCraft.MOD_ID, "command_computer" ) ); GameRegistry.registerTileEntity( TileCommandComputer.class, ComputerCraft.LOWER_ID + " : " + "command_computer" );
GameRegistry.registerTileEntity( TileAdvancedModem.class, new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ); GameRegistry.registerTileEntity( TileAdvancedModem.class, ComputerCraft.LOWER_ID + " : " + "advanced_modem" );
GameRegistry.registerTileEntity( TileSpeaker.class, new ResourceLocation( ComputerCraft.MOD_ID, "speaker" ) ); GameRegistry.registerTileEntity( TileSpeaker.class, ComputerCraft.LOWER_ID + " : " + "speaker" );
GameRegistry.registerTileEntity( TileWiredModemFull.class, new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) ); GameRegistry.registerTileEntity( TileWiredModemFull.class, ComputerCraft.LOWER_ID + " : " + "wired_modem_full" );
// Register peripheral providers // Register peripheral providers
ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() ); ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() );

View File

@@ -6,9 +6,7 @@
package dan200.computercraft.shared.turtle.apis; package dan200.computercraft.shared.turtle.apis;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.ITurtleCommand;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
@@ -22,6 +20,7 @@ import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -99,9 +98,9 @@ public class TurtleAPI implements ILuaAPI
}; };
} }
private Object[] tryCommand( ILuaContext context, ITurtleCommand command ) throws LuaException, InterruptedException private MethodResult tryCommand( ITurtleCommand command ) throws LuaException
{ {
return m_turtle.executeCommand( context, command ); return m_turtle.executeCommand( command );
} }
private int parseSlotNumber( Object[] arguments, int index ) throws LuaException private int parseSlotNumber( Object[] arguments, int index ) throws LuaException
@@ -155,7 +154,8 @@ public class TurtleAPI implements ILuaAPI
} }
@Override @Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException @Nonnull
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -163,89 +163,89 @@ public class TurtleAPI implements ILuaAPI
{ {
// forward // forward
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Forward ) ); return tryCommand( new TurtleMoveCommand( MoveDirection.Forward ) );
} }
case 1: case 1:
{ {
// back // back
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Back ) ); return tryCommand( new TurtleMoveCommand( MoveDirection.Back ) );
} }
case 2: case 2:
{ {
// up // up
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Up ) ); return tryCommand( new TurtleMoveCommand( MoveDirection.Up ) );
} }
case 3: case 3:
{ {
// down // down
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Down ) ); return tryCommand( new TurtleMoveCommand( MoveDirection.Down ) );
} }
case 4: case 4:
{ {
// turnLeft // turnLeft
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Left ) ); return tryCommand( new TurtleTurnCommand( TurnDirection.Left ) );
} }
case 5: case 5:
{ {
// turnRight // turnRight
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Right ) ); return tryCommand( new TurtleTurnCommand( TurnDirection.Right ) );
} }
case 6: case 6:
{ {
// dig // dig
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Forward, side ) ); return tryCommand( new TurtleDigCommand( InteractDirection.Forward, side ) );
} }
case 7: case 7:
{ {
// digUp // digUp
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Up, side ) ); return tryCommand( new TurtleDigCommand( InteractDirection.Up, side ) );
} }
case 8: case 8:
{ {
// digDown // digDown
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDigCommand( InteractDirection.Down, side ) ); return tryCommand( new TurtleDigCommand( InteractDirection.Down, side ) );
} }
case 9: case 9:
{ {
// place // place
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Forward, args ) ); return tryCommand( new TurtlePlaceCommand( InteractDirection.Forward, args ) );
} }
case 10: case 10:
{ {
// placeUp // placeUp
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Up, args ) ); return tryCommand( new TurtlePlaceCommand( InteractDirection.Up, args ) );
} }
case 11: case 11:
{ {
// placeDown // placeDown
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Down, args ) ); return tryCommand( new TurtlePlaceCommand( InteractDirection.Down, args ) );
} }
case 12: case 12:
{ {
// drop // drop
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Forward, count ) ); return tryCommand( new TurtleDropCommand( InteractDirection.Forward, count ) );
} }
case 13: case 13:
{ {
// select // select
int slot = parseSlotNumber( args, 0 ); int slot = parseSlotNumber( args, 0 );
return tryCommand( context, new TurtleSelectCommand( slot ) ); return tryCommand( new TurtleSelectCommand( slot ) );
} }
case 14: case 14:
{ {
@@ -254,11 +254,11 @@ public class TurtleAPI implements ILuaAPI
ItemStack stack = m_turtle.getInventory().getStackInSlot( slot ); ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
if( !stack.isEmpty() ) if( !stack.isEmpty() )
{ {
return new Object[] { stack.getCount() }; return MethodResult.of( stack.getCount() );
} }
else else
{ {
return new Object[] { 0 }; return MethodResult.of( 0 );
} }
} }
case 15: case 15:
@@ -268,172 +268,172 @@ public class TurtleAPI implements ILuaAPI
ItemStack stack = m_turtle.getInventory().getStackInSlot( slot ); ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
if( !stack.isEmpty() ) if( !stack.isEmpty() )
{ {
return new Object[] { return MethodResult.of(
Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount() Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount()
}; );
} }
return new Object[] { 64 }; return MethodResult.of( 64 );
} }
case 16: case 16:
{ {
// detect // detect
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Forward ) ); return tryCommand( new TurtleDetectCommand( InteractDirection.Forward ) );
} }
case 17: case 17:
{ {
// detectUp // detectUp
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Up ) ); return tryCommand( new TurtleDetectCommand( InteractDirection.Up ) );
} }
case 18: case 18:
{ {
// detectDown // detectDown
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Down ) ); return tryCommand( new TurtleDetectCommand( InteractDirection.Down ) );
} }
case 19: case 19:
{ {
// compare // compare
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Forward ) ); return tryCommand( new TurtleCompareCommand( InteractDirection.Forward ) );
} }
case 20: case 20:
{ {
// compareUp // compareUp
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Up ) ); return tryCommand( new TurtleCompareCommand( InteractDirection.Up ) );
} }
case 21: case 21:
{ {
// compareDown // compareDown
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Down ) ); return tryCommand( new TurtleCompareCommand( InteractDirection.Down ) );
} }
case 22: case 22:
{ {
// attack // attack
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Forward, side ) ); return tryCommand( new TurtleAttackCommand( InteractDirection.Forward, side ) );
} }
case 23: case 23:
{ {
// attackUp // attackUp
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Up, side ) ); return tryCommand( new TurtleAttackCommand( InteractDirection.Up, side ) );
} }
case 24: case 24:
{ {
// attackDown // attackDown
Optional<TurtleSide> side = parseSide( args, 0 ); Optional<TurtleSide> side = parseSide( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Down, side ) ); return tryCommand( new TurtleAttackCommand( InteractDirection.Down, side ) );
} }
case 25: case 25:
{ {
// dropUp // dropUp
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Up, count ) ); return tryCommand( new TurtleDropCommand( InteractDirection.Up, count ) );
} }
case 26: case 26:
{ {
// dropDown // dropDown
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleDropCommand( InteractDirection.Down, count ) ); return tryCommand( new TurtleDropCommand( InteractDirection.Down, count ) );
} }
case 27: case 27:
{ {
// suck // suck
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Forward, count ) ); return tryCommand( new TurtleSuckCommand( InteractDirection.Forward, count ) );
} }
case 28: case 28:
{ {
// suckUp // suckUp
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Up, count ) ); return tryCommand( new TurtleSuckCommand( InteractDirection.Up, count ) );
} }
case 29: case 29:
{ {
// suckDown // suckDown
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Down, count ) ); return tryCommand( new TurtleSuckCommand( InteractDirection.Down, count ) );
} }
case 30: case 30:
{ {
// getFuelLevel // getFuelLevel
if( m_turtle.isFuelNeeded() ) if( m_turtle.isFuelNeeded() )
{ {
return new Object[] { m_turtle.getFuelLevel() }; return MethodResult.of( m_turtle.getFuelLevel() );
} }
else else
{ {
return new Object[] { "unlimited" }; return MethodResult.of( "unlimited" );
} }
} }
case 31: case 31:
{ {
// refuel // refuel
int count = parseCount( args, 0 ); int count = parseCount( args, 0 );
return tryCommand( context, new TurtleRefuelCommand( count ) ); return tryCommand( new TurtleRefuelCommand( count ) );
} }
case 32: case 32:
{ {
// compareTo // compareTo
int slot = parseSlotNumber( args, 0 ); int slot = parseSlotNumber( args, 0 );
return tryCommand( context, new TurtleCompareToCommand( slot ) ); return tryCommand( new TurtleCompareToCommand( slot ) );
} }
case 33: case 33:
{ {
// transferTo // transferTo
int slot = parseSlotNumber( args, 0 ); int slot = parseSlotNumber( args, 0 );
int count = parseCount( args, 1 ); int count = parseCount( args, 1 );
return tryCommand( context, new TurtleTransferToCommand( slot, count ) ); return tryCommand( new TurtleTransferToCommand( slot, count ) );
} }
case 34: case 34:
{ {
// getSelectedSlot // getSelectedSlot
return new Object[] { m_turtle.getSelectedSlot() + 1 }; return MethodResult.of( m_turtle.getSelectedSlot() + 1 );
} }
case 35: case 35:
{ {
// getFuelLimit // getFuelLimit
if( m_turtle.isFuelNeeded() ) if( m_turtle.isFuelNeeded() )
{ {
return new Object[] { m_turtle.getFuelLimit() }; return MethodResult.of( m_turtle.getFuelLimit() );
} }
else else
{ {
return new Object[] { "unlimited" }; return MethodResult.of( "unlimited" );
} }
} }
case 36: case 36:
{ {
// equipLeft // equipLeft
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Left ) ); return tryCommand( new TurtleEquipCommand( TurtleSide.Left ) );
} }
case 37: case 37:
{ {
// equipRight // equipRight
m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Right ) ); return tryCommand( new TurtleEquipCommand( TurtleSide.Right ) );
} }
case 38: case 38:
{ {
// inspect // inspect
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Forward ) ); return tryCommand( new TurtleInspectCommand( InteractDirection.Forward ) );
} }
case 39: case 39:
{ {
// inspectUp // inspectUp
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) ); return tryCommand( new TurtleInspectCommand( InteractDirection.Up ) );
} }
case 40: case 40:
{ {
// inspectDown // inspectDown
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) ); return tryCommand( new TurtleInspectCommand( InteractDirection.Down ) );
} }
case 41: case 41:
{ {
@@ -455,20 +455,28 @@ public class TurtleAPI implements ILuaAPI
TurtleActionEvent event = new TurtleActionEvent( m_turtle, TurtleAction.INSPECT_ITEM ); TurtleActionEvent event = new TurtleActionEvent( m_turtle, TurtleAction.INSPECT_ITEM );
if( MinecraftForge.EVENT_BUS.post( event ) ) if( MinecraftForge.EVENT_BUS.post( event ) )
{ {
return new Object[] { false, event.getFailureMessage() }; return MethodResult.of( false, event.getFailureMessage() );
} }
return new Object[] { table }; return MethodResult.of( table );
} }
else else
{ {
return new Object[] { null }; return MethodResult.of( new Object[] { null } );
} }
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Override
@Nullable
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} }

View File

@@ -10,7 +10,9 @@ import com.google.common.base.Objects;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.*; import dan200.computercraft.api.turtle.*;
import dan200.computercraft.shared.computer.blocks.ComputerProxy; import dan200.computercraft.shared.computer.blocks.ComputerProxy;
@@ -761,6 +763,7 @@ public class TurtleBrain implements ITurtleAccess
@Nonnull @Nonnull
@Override @Override
@Deprecated
public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException
{ {
if( getWorld().isRemote ) if( getWorld().isRemote )
@@ -787,6 +790,38 @@ public class TurtleBrain implements ITurtleAccess
} }
} }
@Nonnull
@Override
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
{
if( getWorld().isRemote )
{
throw new UnsupportedOperationException();
}
// Issue command
int commandID = issueCommand( command );
// Wait for response
return MethodResult.pullEvent( "turtle_response", new ILuaFunction()
{
@Nonnull
@Override
public MethodResult call( Object[] response )
{
if( response.length >= 3 && response[ 1 ] instanceof Number && ((Number) response[ 1 ]).intValue() == commandID
&& response[ 2 ] instanceof Boolean )
{
Object[] returnValues = new Object[ response.length - 2 ];
System.arraycopy( response, 2, returnValues, 0, returnValues.length );
return MethodResult.of( returnValues );
}
return MethodResult.pullEvent( "turtle_response", this );
}
} );
}
@Override @Override
public void playAnimation( @Nonnull TurtleAnimation animation ) public void playAnimation( @Nonnull TurtleAnimation animation )
{ {

View File

@@ -6,19 +6,21 @@
package dan200.computercraft.shared.turtle.upgrades; package dan200.computercraft.shared.turtle.upgrades;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.shared.turtle.core.TurtleCraftCommand; import dan200.computercraft.shared.turtle.core.TurtleCraftCommand;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt; import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class CraftingTablePeripheral public class CraftingTablePeripheral implements IPeripheral
implements IPeripheral
{ {
private final ITurtleAccess m_turtle; private final ITurtleAccess m_turtle;
@@ -26,7 +28,7 @@ public class CraftingTablePeripheral
{ {
m_turtle = turtle; m_turtle = turtle;
} }
// IPeripheral implementation // IPeripheral implementation
@Nonnull @Nonnull
@@ -35,7 +37,7 @@ public class CraftingTablePeripheral
{ {
return "workbench"; return "workbench";
} }
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()
@@ -44,7 +46,7 @@ public class CraftingTablePeripheral
"craft", "craft",
}; };
} }
private int parseCount( Object[] arguments ) throws LuaException private int parseCount( Object[] arguments ) throws LuaException
{ {
int count = optInt( arguments, 0, 64 ); int count = optInt( arguments, 0, 64 );
@@ -54,9 +56,10 @@ public class CraftingTablePeripheral
} }
return count; return count;
} }
@Override @Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException @Nonnull
public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{ {
switch( method ) switch( method )
{ {
@@ -64,15 +67,24 @@ public class CraftingTablePeripheral
{ {
// craft // craft
final int limit = parseCount( arguments ); final int limit = parseCount( arguments );
return m_turtle.executeCommand( context, new TurtleCraftCommand( limit ) ); return m_turtle.executeCommand( new TurtleCraftCommand( limit ) );
} }
default: default:
{ {
return null; return MethodResult.empty();
} }
} }
} }
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override @Override
public boolean equals( IPeripheral other ) public boolean equals( IPeripheral other )
{ {

View File

@@ -10,7 +10,6 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.*; import dan200.computercraft.api.turtle.*;
import dan200.computercraft.shared.peripheral.PeripheralType; import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory; import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral; import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.IBakedModel;
@@ -18,9 +17,9 @@ import net.minecraft.client.renderer.block.model.ModelManager;
import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
@@ -38,7 +37,7 @@ public class TurtleModem implements ITurtleUpgrade
public Peripheral( ITurtleAccess turtle, boolean advanced ) public Peripheral( ITurtleAccess turtle, boolean advanced )
{ {
super( new ModemState(), advanced ); super( advanced );
m_turtle = turtle; m_turtle = turtle;
} }
@@ -222,12 +221,12 @@ public class TurtleModem implements ITurtleUpgrade
if( !turtle.getWorld().isRemote ) if( !turtle.getWorld().isRemote )
{ {
IPeripheral peripheral = turtle.getPeripheral( side ); IPeripheral peripheral = turtle.getPeripheral( side );
if( peripheral instanceof Peripheral ) if( peripheral != null && peripheral instanceof Peripheral )
{ {
ModemState state = ((Peripheral) peripheral).getModemState(); Peripheral modemPeripheral = (Peripheral)peripheral;
if( state.pollChanged() ) if( modemPeripheral.pollChanged() )
{ {
turtle.getUpgradeNBTData( side ).setBoolean( "active", state.isOpen() ); turtle.getUpgradeNBTData( side ).setBoolean( "active", modemPeripheral.isActive() );
turtle.updateUpgradeNBTData( side ); turtle.updateUpgradeNBTData( side );
} }
} }

View File

@@ -1,78 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ThreadFactory;
/**
* Provides some utilities to create thread groups
*/
public final class ThreadUtils
{
private static final ThreadGroup baseGroup = new ThreadGroup( "ComputerCraft" );
private ThreadUtils()
{
}
/**
* Get the base thread group, that all off-thread ComputerCraft activities are run on.
*
* @return The ComputerCraft group.
*/
public static ThreadGroup group()
{
return baseGroup;
}
/**
* Construct a group under ComputerCraft's shared group
*
* @param name The group's name. This will be prefixed with "ComputerCraft-".
* @return The constructed thread group.
*/
public static ThreadGroup group( String name )
{
return new ThreadGroup( baseGroup, baseGroup.getName() + "-" + name );
}
/**
* Create a new {@link ThreadFactoryBuilder}, which constructs threads under a group of the given {@code name}.
*
* Each thread will be of the format {@code ComputerCraft-<name>-<number>}, and belong to a group
* called {@code ComputerCraft-<name>} (which in turn will be a child group of the main {@code ComputerCraft} group.
*
* @param name The name for the thread group and child threads.
* @return The constructed thread factory builder, which may be extended with other properties.
* @see #factory(String)
*/
public static ThreadFactoryBuilder builder( String name )
{
ThreadGroup group = group( name );
return new ThreadFactoryBuilder()
.setDaemon( true )
.setNameFormat( group.getName().replace( "%", "%%" ) + "-%d" )
.setThreadFactory( x -> new Thread( group, x ) );
}
/**
* Create a new {@link ThreadFactory}, which constructs threads under a group of the given {@code name}.
*
* Each thread will be of the format {@code ComputerCraft-<name>-<number>}, and belong to a group
* called {@code ComputerCraft-<name>} (which in turn will be a child group of the main {@code ComputerCraft} group.
*
* @param name The name for the thread group and child threads.
* @return The constructed thread factory.
* @see #builder(String)
*/
public static ThreadFactory factory( String name )
{
return builder( name ).build();
}
}

View File

@@ -1,67 +0,0 @@
tile.computercraft:computer.name=Computer
tile.computercraft:advanced_computer.name=Erweiterter Computer
tile.computercraft:drive.name=Diskettenlaufwerk
tile.computercraft:printer.name=Drucker
tile.computercraft:monitor.name=Monitor
tile.computercraft:advanced_monitor.name=Erweiterter Monitor
tile.computercraft:wireless_modem.name=Kabelloses Modem
tile.computercraft:wired_modem.name=Kabelmodem
tile.computercraft:cable.name=Netzwerkkabel
tile.computercraft:command_computer.name=Befehlscomputer
tile.computercraft:advanced_modem.name=Endermodem
tile.computercraft:speaker.name=Lautsprecher
tile.computercraft:turtle.name=Turtle
tile.computercraft:turtle.upgraded.name=Turtle (%s)
tile.computercraft:turtle.upgraded_twice.name=Turtle (%s, %s)
tile.computercraft:advanced_turtle.name=Erweiterte Turtle
tile.computercraft:advanced_turtle.upgraded.name=Erweiterte Turtle (%s)
tile.computercraft:advanced_turtle.upgraded_twice.name=Erweiterte Turtle (%s, %s)
item.computercraft:disk.name=Diskette
item.computercraft:treasure_disk.name=Diskette
item.computercraft:page.name=Gedruckte Seite
item.computercraft:pages.name=Gedruckte Seiten
item.computercraft:book.name=Gedrucktes Buch
item.computercraft:pocket_computer.name=Taschencomputer
item.computercraft:pocket_computer.upgraded.name=Taschencomputer (%s)
item.computercraft:advanced_pocket_computer.name=Erweiterter Taschencomputer
item.computercraft:advanced_pocket_computer.upgraded.name=Erweiterter Taschencomputer (%s)
upgrade.minecraft:diamond_sword.adjective=Nahkampf
upgrade.minecraft:diamond_shovel.adjective=Graben
upgrade.minecraft:diamond_pickaxe.adjective=Bergbau
upgrade.minecraft:diamond_axe.adjective=Holzfällen
upgrade.minecraft:diamond_hoe.adjective=Ackerbau
upgrade.computercraft:wireless_modem.adjective=Kabellos
upgrade.minecraft:crafting_table.adjective=Fabrizierung
upgrade.computercraft:advanced_modem.adjective=Ender
upgrade.computercraft:speaker.adjective=Laut
gui.computercraft:wired_modem.peripheral_connected=Peripheriegerät "%s" mit dem Netzwerk verbunden
gui.computercraft:wired_modem.peripheral_disconnected=Peripheriegerät "%s" vom Netzwerk getrennt
gui.computercraft:config.http_enable=HTTP-API aktivieren
gui.computercraft:config.http_websocket_enable=HTTP-Websockets aktivieren
gui.computercraft:config.http_whitelist=HTTP-Whitelist
gui.computercraft:config.http_blacklist=HTTP-Blacklist
gui.computercraft:config.disable_lua51_features=Lua 5.1-Funktionen deaktivieren
gui.computercraft:config.default_computer_settings=Computer-Standardeinstellungen
gui.computercraft:config.debug_enable=Debug-Library aktivieren
gui.computercraft:config.log_peripheral_errors=Peripheriefehler loggen
gui.computercraft:config.enable_command_block=Befehlsblöcke als Peripheriegerät erlauben
gui.computercraft:config.modem_range=Modemreichweite (normal)
gui.computercraft:config.modem_high_altitude_range=Modemreichweite (in großer Höhe)
gui.computercraft:config.modem_range_during_storm=Modemreichweite (bei schlechtem Wetter)
gui.computercraft:config.modem_high_altitude_range_during_storm=Modemreichweite (in großer Höhe bei schlechtem Wetter)
gui.computercraft:config.computer_space_limit=Speicherplatz von Computern (bytes)
gui.computercraft:config.floppy_space_limit=Speicherplatz von Disketten (bytes)
gui.computercraft:config.turtles_need_fuel=Treibstoffverbrauch aktivieren
gui.computercraft:config.turtle_fuel_limit=Treibstofflimit von Turtles
gui.computercraft:config.advanced_turtle_fuel_limit=Treibstofflimit von Erweiterten Turtles
gui.computercraft:config.turtles_obey_block_protection=Turtles unterliegen Blockschutzrichtlinien
gui.computercraft:config.turtles_can_push=Turtles können Entities bewegen
gui.computercraft:config.turtle_disabled_actions=Deaktivierte Turtle-Aktionen
gui.computercraft:config.maximum_files_open=Maximalanzahl an gleichzeitig offenen Dateien je Computer
gui.computercraft:config.max_notes_per_tick=Maximalanzahl an Noten, die ein Computer gleichzeitig spielen kann

View File

@@ -1,249 +1,202 @@
-- Definition for the IO API -- Definition for the IO API
local typeOf = _G.type
--- If we return nil then close the file, as we've reached the end. local g_defaultInput = {
-- We use this weird wrapper function as we wish to preserve the varargs bFileHandle = true,
local function checkResult(handle, ...) bClosed = false,
if ... == nil and handle._autoclose and not handle._closed then handle:close() end close = function( self )
return ... end,
end read = function( self, _sFormat )
if _sFormat and _sFormat ~= "*l" then
local handleMetatable error( "Unsupported format" )
handleMetatable = { end
__name = "FILE*", return _G.read()
__tostring = function(self) end,
if self._closed then lines = function( self )
return "file (closed)" return function()
else return _G.read()
local hash = tostring(self._handle):match("table: (%x+)") end
return "file (" .. hash .. ")" end,
end
end,
__index = {
close = function(self)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if handle.close then
self._closed = true
handle.close()
return true
else
return nil, "attempt to close standard stream"
end
end,
flush = function(self)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if handle.flush then handle.flush() end
end,
lines = function(self, ...)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if not handle.read then return nil, "file is not readable" end
local args = table.pack(...)
return function() return checkResult(self, self:read(table.unpack(args, 1, args.n))) end
end,
read = function(self, ...)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if not handle.read and not handle.readLine then return nil, "Not opened for reading" end
local n = select('#', ...)
local output = {}
for i = 1, n do
local arg = select(i, ...)
local res
if typeOf(arg) == "number" then
if handle.read then res = handle.read(arg) end
elseif typeOf(arg) == "string" then
local format = arg:gsub("^%*", ""):sub(1, 1)
if format == "l" then
if handle.readLine then res = handle.readLine() end
elseif format == "L" and handle.readLine then
if handle.readLine then res = handle.readLine(true) end
elseif format == "a" then
if handle.readAll then res = handle.readAll() or "" end
elseif format == "n" then
res = nil -- Skip this format as we can't really handle it
else
error("bad argument #" .. i .. " (invalid format)", 2)
end
else
error("bad argument #" .. i .. " (expected string, got " .. typeOf(arg) .. ")", 2)
end
output[i] = res
if not res then break end
end
-- Default to "l" if possible
if n == 0 and handle.readLine then return handle.readLine() end
return table.unpack(output, 1, n)
end,
seek = function(self, whence, offset)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if not handle.seek then return nil, "file is not seekable" end
-- It's a tail call, so error positions are preserved
return handle.seek(whence, offset)
end,
setvbuf = function(self, mode, size) end,
write = function(self, ...)
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
end
if self._closed then error("attempt to use a closed file", 2) end
local handle = self._handle
if not handle.write then return nil, "file is not writable" end
local n = select("#", ...)
for i = 1, n do handle.write(select(i, ...)) end
return self
end,
},
} }
local defaultInput = setmetatable({ local g_defaultOutput = {
_handle = { readLine = _G.read } bFileHandle = true,
}, handleMetatable) bClosed = false,
close = function( self )
end,
write = function( self, ... )
local nLimit = select("#", ... )
for n = 1, nLimit do
_G.write( select( n, ... ) )
end
end,
flush = function( self )
end,
}
local defaultOutput = setmetatable({ local g_currentInput = g_defaultInput
_handle = { write = _G.write } local g_currentOutput = g_defaultOutput
}, handleMetatable)
local defaultError = setmetatable({ function close( _file )
_handle = { (_file or g_currentOutput):close()
write = function(...)
local oldColour
if term.isColour() then
oldColour = term.getTextColour()
term.setTextColour(colors.red)
end
_G.write(...)
if term.isColour() then term.setTextColour(oldColour) end
end,
}
}, handleMetatable)
local currentInput = defaultInput
local currentOutput = defaultOutput
stdin = defaultInput
stdout = defaultOutput
stderr = defaultError
function close(_file)
if _file == nil then return currentOutput:close() end
if typeOf(_file) ~= "table" or getmetatable(_file) ~= handleMetatable then
error("bad argument #1 (FILE expected, got " .. typeOf(_file) .. ")", 2)
end
return _file:close()
end end
function flush() function flush()
return currentOutput:flush() g_currentOutput:flush()
end end
function input(_arg) function input( _arg )
if typeOf(_arg) == "string" then if _G.type( _arg ) == "string" then
local res, err = open(_arg, "rb") g_currentInput = open( _arg, "r" )
if not res then error(err, 2) end elseif _G.type( _arg ) == "table" then
currentInput = res g_currentInput = _arg
elseif typeOf(_arg) == "table" and getmetatable(_arg) == handleMetatable then elseif _G.type( _arg ) == "nil" then
currentInput = _arg return g_currentInput
elseif _arg ~= nil then else
error("bad argument #1 (FILE expected, got " .. typeOf(_arg) .. ")", 2) error( "bad argument #1 (expected string/table/nil, got " .. _G.type( _arg ) .. ")", 2 )
end
end
function lines( _sFileName )
if _G.type( _sFileName ) ~= "string" then
error( "bad argument #1 (expected string, got " .. _G.type( _sFileName ) .. ")", 2 )
end end
if _sFileName then
return currentInput return open( _sFileName, "r" ):lines()
else
return g_currentInput:lines()
end
end end
function lines(_sFileName) function open( _sPath, _sMode )
if _sFileName ~= nil and typeOf(_sFileName) ~= "string" then if _G.type( _sPath ) ~= "string" then
error("bad argument #1 (expected string, got " .. typeOf(_sFileName) .. ")", 2) error( "bad argument #1 (expected string, got " .. _G.type( _sPath ) .. ")", 2 )
end end
if _sFileName then if _sMode ~= nil and _G.type( _sMode ) ~= "string" then
local ok, err = open(_sFileName, "rb") error( "bad argument #2 (expected string, got " .. _G.type( _sMode ) .. ")", 2 )
if not ok then error(err, 2) end
-- We set this magic flag to mark this file as being opened by io.lines and so should be
-- closed automatically
ok._autoclose = true
return ok:lines()
else
return currentInput:lines()
end end
local sMode = _sMode or "r"
local file, err = fs.open( _sPath, sMode )
if not file then
return nil, err
end
if sMode == "r"then
return {
bFileHandle = true,
bClosed = false,
close = function( self )
file.close()
self.bClosed = true
end,
read = function( self, _sFormat )
local sFormat = _sFormat or "*l"
if sFormat == "*l" then
return file.readLine()
elseif sFormat == "*a" then
return file.readAll()
elseif _G.type( sFormat ) == "number" then
return file.read( sFormat )
else
error( "Unsupported format", 2 )
end
return nil
end,
lines = function( self )
return function()
local sLine = file.readLine()
if sLine == nil then
file.close()
self.bClosed = true
end
return sLine
end
end,
}
elseif sMode == "w" or sMode == "a" then
return {
bFileHandle = true,
bClosed = false,
close = function( self )
file.close()
self.bClosed = true
end,
write = function( self, ... )
local nLimit = select("#", ... )
for n = 1, nLimit do
file.write( select( n, ... ) )
end
end,
flush = function( self )
file.flush()
end,
}
elseif sMode == "rb" then
return {
bFileHandle = true,
bClosed = false,
close = function( self )
file.close()
self.bClosed = true
end,
read = function( self )
return file.read()
end,
}
elseif sMode == "wb" or sMode == "ab" then
return {
bFileHandle = true,
bClosed = false,
close = function( self )
file.close()
self.bClosed = true
end,
write = function( self, ... )
local nLimit = select("#", ... )
for n = 1, nLimit do
file.write( select( n, ... ) )
end
end,
flush = function( self )
file.flush()
end,
}
else
file.close()
error( "Unsupported mode", 2 )
end
end end
function open(_sPath, _sMode) function output( _arg )
if typeOf(_sPath) ~= "string" then if _G.type( _arg ) == "string" then
error("bad argument #1 (expected string, got " .. typeOf(_sPath) .. ")", 2) g_currentOutput = open( _arg, "w" )
end elseif _G.type( _arg ) == "table" then
if _sMode ~= nil and typeOf(_sMode) ~= "string" then g_currentOutput = _arg
error("bad argument #2 (expected string, got " .. typeOf(_sMode) .. ")", 2) elseif _G.type( _arg ) == "nil" then
end return g_currentOutput
else
local sMode = _sMode and _sMode:gsub("%+", "") or "rb" error( "bad argument #1 (expected string/table/nil, got " .. _G.type( _arg ) .. ")", 2 )
local file, err = fs.open(_sPath, sMode) end
if not file then return nil, err end
return setmetatable({ _handle = file }, handleMetatable)
end end
function output(_arg) function read( ... )
if typeOf(_arg) == "string" then return input():read( ... )
local res, err = open(_arg, "w")
if not res then error(err, 2) end
currentOutput = res
elseif typeOf(_arg) == "table" and getmetatable(_arg) == handleMetatable then
currentOutput = _arg
elseif _arg ~= nil then
error("bad argument #1 (FILE expected, got " .. typeOf(_arg) .. ")", 2)
end
return currentOutput
end end
function read(...) function type( _handle )
return currentInput:read(...) if _G.type( _handle ) == "table" and _handle.bFileHandle == true then
if _handle.bClosed then
return "closed file"
else
return "file"
end
end
return nil
end end
function type(handle) function write( ... )
if typeOf(handle) == "table" and getmetatable(handle) == handleMetatable then return output():write( ... )
if handle._closed then
return "closed file"
else
return "file"
end
end
return nil
end
function write(...)
return currentOutput:write(...)
end end

View File

@@ -258,10 +258,6 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
end end
end end
function window.getCursorBlink()
return bCursorBlink
end
local function isColor() local function isColor()
return parent.isColor() return parent.isColor()
end end

View File

@@ -44,7 +44,7 @@ local ok, error = pcall( function()
if sEvent == "modem_message" then if sEvent == "modem_message" then
-- Got a modem message, rebroadcast it if it's a rednet thing -- Got a modem message, rebroadcast it if it's a rednet thing
if nChannel == rednet.CHANNEL_REPEAT then if nChannel == rednet.CHANNEL_REPEAT then
if type( tMessage ) == "table" and tMessage.nMessageID and tMessage.nRecipient and type(tMessage.nRecipient) == "number" then if type( tMessage ) == "table" and tMessage.nMessageID and tMessage.nRecipient then
if not tReceivedMessages[ tMessage.nMessageID ] then if not tReceivedMessages[ tMessage.nMessageID ] then
-- Ensure we only repeat a message once -- Ensure we only repeat a message once
tReceivedMessages[ tMessage.nMessageID ] = true tReceivedMessages[ tMessage.nMessageID ] = true

View File

@@ -1,58 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core;
import com.google.common.io.Files;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
public class FileSystemTest
{
private static final File ROOT = new File( "test-files/filesystem" );
/**
* Ensures writing a file truncates it.
*/
@Test
public void testWriteTruncates() throws FileSystemException, LuaException, IOException
{
IWritableMount writableMount = new FileMount( ROOT, 1000000 );
FileSystem fs = new FileSystem( "hdd", writableMount );
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "This is a long line" );
wrapper.call( "close" );
}
assertEquals( "This is a long line", Files.toString( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ) );
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "Tiny line" );
wrapper.call( "close" );
}
assertEquals( "Tiny line", Files.toString( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ) );
}
}

View File

@@ -1,90 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ObjectWrapper implements ILuaContext
{
private final ILuaObject object;
private final String[] methods;
public ObjectWrapper( ILuaObject object )
{
this.object = object;
this.methods = object.getMethodNames();
}
private int findMethod( String method )
{
for( int i = 0; i < methods.length; i++ )
{
if( method.equals( methods[i] ) ) return i;
}
return -1;
}
public boolean hasMethod( String method )
{
return findMethod( method ) >= 0;
}
public Object[] call( String name, Object... args ) throws LuaException
{
int method = findMethod( name );
if( method < 0 ) throw new IllegalStateException( "No such method '" + name + "'" );
try
{
return object.callMethod( this, method, args );
}
catch( InterruptedException e )
{
throw new IllegalStateException( "Should never be interrupted", e );
}
}
@Nonnull
@Override
public Object[] pullEvent( @Nullable String filter )
{
throw new IllegalStateException( "Method should never yield" );
}
@Nonnull
@Override
public Object[] pullEventRaw( @Nullable String filter )
{
throw new IllegalStateException( "Method should never yield" );
}
@Nonnull
@Override
public Object[] yield( @Nullable Object[] arguments )
{
throw new IllegalStateException( "Method should never yield" );
}
@Nullable
@Override
public Object[] executeMainThreadTask( @Nonnull ILuaTask task )
{
throw new IllegalStateException( "Method should never yield" );
}
@Override
public long issueMainThreadTask( @Nonnull ILuaTask task )
{
throw new IllegalStateException( "Method should never queue events" );
}
}

View File

@@ -0,0 +1,91 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import net.minecraftforge.fml.common.Loader;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
public class FakeComputerEnvironment implements IComputerEnvironment
{
private final boolean colour;
private final int id;
public FakeComputerEnvironment( int id, boolean colour )
{
this.colour = colour;
this.id = id;
}
@Override
public int getDay()
{
return 0;
}
@Override
public double getTimeOfDay()
{
return 0;
}
@Override
public boolean isColour()
{
return colour;
}
@Override
public int assignNewID()
{
return id;
}
@Override
public IWritableMount createSaveDirMount( String subPath, long capacity )
{
return new FileMount( new File( "computer/" + subPath ), capacity );
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
String fullPath = "assets/" + domain + "/" + subPath;
URL url = ComputerCraft.class.getProtectionDomain().getCodeSource().getLocation();
File file = new File( url.getPath(), fullPath );
if( !file.exists() ) file = new File( "src/main/resources", fullPath );
if( !file.exists() ) throw new RuntimeException( "Cannot find ROM in " + file );
return new FileMount( file, 0 );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
String fullPath = "assets/" + domain + "/" + subPath;
return ComputerCraft.class.getClassLoader().getResourceAsStream( fullPath );
}
@Override
public long getComputerSpaceLimit()
{
return ComputerCraft.computerSpaceLimit;
}
@Override
public String getHostString()
{
return "ComputerCraft ${version} (Minecraft " + Loader.MC_VERSION + ")";
}
}

View File

@@ -0,0 +1,155 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.terminal.Terminal;
import org.apache.logging.log4j.LogManager;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.function.Consumer;
import static org.junit.Assert.fail;
public final class RunOnComputer
{
public static final int STARTUP_TIMEOUT = 10;
public static final int RUN_TIMEOUT = 100;
public static void run( String task ) throws Exception
{
run( task, x -> {
} );
}
public static void run( String task, Consumer<Computer> setup ) throws Exception
{
if( ComputerCraft.log == null ) ComputerCraft.log = LogManager.getLogger( "computercraft" );
ComputerCraft.logPeripheralErrors = true;
// Setup computer
Terminal terminal = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );
Computer computer = new Computer( new FakeComputerEnvironment( 0, true ), terminal, 0 );
// Register APIS
TestAPI api = new TestAPI( computer );
computer.addAPI( api );
setup.accept( computer );
// Setup the startup file
try( OutputStream stream = computer.getRootMount().openForWrite( "startup.lua" ) )
{
String program = "" +
"local function exec()\n" +
" " + task + "\n" +
"end\n" +
"test.finish(pcall(exec))\n";
stream.write( program.getBytes( StandardCharsets.UTF_8 ) );
}
// Turn on
ComputerThread.start();
computer.turnOn();
// Run until shutdown or we timeout
boolean everOn = false;
int ticks = 0;
do
{
computer.advance( 0.05 );
MainThread.executePendingTasks();
Thread.sleep( 50 );
ticks++;
everOn |= computer.isOn();
}
while( (computer.isOn() || (!everOn && ticks < STARTUP_TIMEOUT)) && ticks <= RUN_TIMEOUT );
// If we never finished (say, startup errored) then print the terminal. Otherwise throw the error
// where needed.
if( !api.finished )
{
int height = terminal.getHeight() - 1;
while( height >= 0 && terminal.getLine( height ).toString().trim().isEmpty() ) height--;
for( int y = 0; y <= height; y++ )
{
System.out.printf( "%2d | %s\n", y + 1, terminal.getLine( y ) );
}
fail( "Computer never finished" );
}
else if( api.error != null )
{
fail( api.error );
}
ComputerThread.stop();
}
private static class TestAPI implements ILuaAPI
{
private final Computer computer;
private boolean finished = false;
private String error;
private TestAPI( Computer computer )
{
this.computer = computer;
}
@Override
public String[] getNames()
{
return new String[]{ "test" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "log", "finish" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
switch( method )
{
case 0:
ComputerCraft.log.info( Objects.toString( arguments.length <= 0 ? null : arguments[0] ) );
return MethodResult.empty();
case 1:
{
if( arguments.length <= 0 || arguments[0] == null || arguments[0] == Boolean.FALSE )
{
error = Objects.toString( arguments.length <= 1 ? null : arguments[1] );
}
finished = true;
computer.shutdown();
return MethodResult.empty();
}
default:
return MethodResult.empty();
}
}
}
}

View File

@@ -0,0 +1,243 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.computer.RunOnComputer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
@RunWith( Parameterized.class )
public class CobaltWrapperFunctionTest
{
@Parameterized.Parameter( 0 )
public String name;
@Parameterized.Parameter( 1 )
public String code;
@Parameterized.Parameters( name = "{0}" )
public static Collection<Object[]> parameters()
{
return Arrays.stream( new Object[][]{
new Object[]{ "empty", "assert(select('#', funcs.empty()) == 0)" },
new Object[]{ "identity", "assert(select('#', funcs.identity(1, 2, 3)) == 3)" },
new Object[]{ "pullEvent", "os.queueEvent('test') assert(funcs.pullEvent() == 'test')" },
new Object[]{ "pullEventTerminate", "os.queueEvent('terminate') assert(not pcall(funcs.pullEvent))" },
new Object[]{ "pullEventRaw", "os.queueEvent('test') assert(funcs.pullEventRaw() == 'test')" },
new Object[]{ "pullEventRawTerminate", "os.queueEvent('terminate') assert(funcs.pullEventRaw() == 'terminate')" },
new Object[]{ "mainThread", "assert(funcs.mainThread() == 1)" },
new Object[]{ "mainThreadMany", "for i = 1, 4 do assert(funcs.mainThread() == 1) end" }
} ).collect( Collectors.toList() );
}
/**
* Tests executing functions defined through the {@link MethodResult} API.
*/
@Test
public void testMethodResult() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new MethodResultAPI() ) );
}
/**
* Tests executing functions defined through the {@link MethodResult} API called with the
* {@link ILuaContext} evaluator.
*/
@Test
public void testMethodResultEvaluate() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new MethodResultAPI() )
{
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
} ) );
}
/**
* Tests using {@link MethodResult#then(ILuaFunction)} afterwards
*/
@Test
public void testMethodResultThen() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new MethodResultAPI() ) {
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return super.callMethod( context, method, arguments )
.then( x -> MethodResult.onMainThread( () -> MethodResult.of( x ).then( MethodResult::of ) ) )
.then( MethodResult::of );
}
} ) );
}
/**
* Tests executing functions defined through the {@link ILuaContext} API.
*/
@Test
public void testLuaContext() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new LuaContextAPI() ) );
}
/**
* Tests executing functions defined through the {@link ILuaContext} API called with the
* {@link MethodResult} evaluator.
*/
@Test
public void testWithLuaContext() throws Exception
{
RunOnComputer.run( code, c -> c.addAPI( new WrapperAPI( new LuaContextAPI() )
{
@Nonnull
@Override
@SuppressWarnings( "deprecation" )
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( c -> callMethod( c, method, arguments ) );
}
} ) );
}
private static class MethodResultAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[]{ "funcs" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "empty", "identity", "pullEvent", "pullEventRaw", "mainThread" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
switch( method )
{
case 0:
return MethodResult.empty();
case 1:
return MethodResult.of( arguments );
case 2:
return MethodResult.pullEvent( "test" );
case 3:
return MethodResult.pullEventRaw( "test" );
case 4:
return MethodResult.onMainThread( () -> MethodResult.of( 1 ) );
default:
return MethodResult.empty();
}
}
}
private static class LuaContextAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[]{ "funcs" };
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "empty", "identity", "pullEvent", "pullEventRaw", "mainThread" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
switch( method )
{
case 0:
return null;
case 1:
return arguments;
case 2:
return context.pullEvent( "test" );
case 3:
return context.pullEventRaw( "test" );
case 4:
return context.executeMainThreadTask( () -> new Object[]{ 1 } );
default:
return null;
}
}
}
public static class WrapperAPI implements ILuaAPI
{
private final ILuaAPI api;
public WrapperAPI( ILuaAPI api )
{
this.api = api;
}
@Override
public String[] getNames()
{
return api.getNames();
}
@Nonnull
@Override
public String[] getMethodNames()
{
return api.getMethodNames();
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return api.callMethod( context, method, arguments );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return api.callMethod( context, method, arguments );
}
}
}

Some files were not shown because too many files have changed in this diff Show More