mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 22:17:39 +00:00
Compare commits
7 Commits
v1.80pr1.1
...
feature/on
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8819f2559d | ||
![]() |
4b741739e8 | ||
![]() |
f23acef2dd | ||
![]() |
ac8444b364 | ||
![]() |
3b4c1eac1c | ||
![]() |
7cc77cb1ed | ||
![]() |
0f70d68d0d |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,12 +1,12 @@
|
||||
/build
|
||||
/out
|
||||
/run
|
||||
build
|
||||
out
|
||||
run
|
||||
deploy
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iml
|
||||
.idea
|
||||
.gradle
|
||||
/luaj-2.0.3/lib
|
||||
/luaj-2.0.3/*.jar
|
||||
luaj-2.0.3/lib
|
||||
luaj-2.0.3/*.jar
|
||||
*.DS_Store
|
||||
/test-files
|
||||
|
@@ -28,8 +28,6 @@ to see the full changes, but here's a couple of the more interesting changes:
|
||||
computers remotely.
|
||||
- Add full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is
|
||||
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?
|
||||
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
|
||||
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:
|
||||
|
||||
|
13
build.gradle
13
build.gradle
@@ -23,7 +23,7 @@ apply plugin: 'org.ajoberstar.grgit'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'maven'
|
||||
|
||||
version = "1.80pr1.11"
|
||||
version = "1.80pr1.8"
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked"
|
||||
|
||||
@@ -42,6 +42,7 @@ minecraft {
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "JEI"
|
||||
url = "http://dvs1.progwml6.com/files/maven"
|
||||
@@ -66,7 +67,7 @@ dependencies {
|
||||
|
||||
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'
|
||||
|
||||
@@ -131,7 +132,7 @@ curseforge {
|
||||
project {
|
||||
id = '282001'
|
||||
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 }
|
||||
runServer.outputs.upToDateWhen { false }
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
events "failed", "standardOut", "standardError"
|
||||
}
|
||||
}
|
||||
|
12
build_luaj.sh
Executable file
12
build_luaj.sh
Executable 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
10
codesize.sh
Executable 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
21
deploy.sh
Executable 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
BIN
libs/luaj-jse-2.0.3.jar
Normal file
Binary file not shown.
7
setup.bat
Normal file
7
setup.bat
Normal 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
14
setup.sh
Executable 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."
|
@@ -26,9 +26,11 @@ import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.AddressPredicate;
|
||||
import dan200.computercraft.core.filesystem.ComboMount;
|
||||
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.tracking.Tracking;
|
||||
import dan200.computercraft.shared.command.CommandComputer;
|
||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
|
||||
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.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.CreativeTabMain;
|
||||
import dan200.computercraft.shared.util.IDAssigner;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import dan200.computercraft.shared.wired.CapabilityWiredElement;
|
||||
import dan200.computercraft.shared.wired.WiredNode;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -98,8 +97,6 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -111,12 +108,12 @@ import java.util.zip.ZipFile;
|
||||
|
||||
@Mod(
|
||||
modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
|
||||
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory",
|
||||
dependencies = "required:forge@[14.23.4.2746,)"
|
||||
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory"
|
||||
)
|
||||
public class ComputerCraft
|
||||
{
|
||||
public static final String MOD_ID = "computercraft";
|
||||
public static final String LOWER_ID = "computercraft";
|
||||
|
||||
// GUI IDs
|
||||
public static final int diskDriveGUIID = 100;
|
||||
@@ -570,7 +567,7 @@ public class ComputerCraft
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
}
|
||||
|
||||
|
||||
// Pack useful terminal information into the various coordinate bits.
|
||||
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement
|
||||
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
|
||||
@@ -925,12 +922,11 @@ public class ComputerCraft
|
||||
{
|
||||
try
|
||||
{
|
||||
FileSystem fs = FileSystems.newFileSystem( modJar.toPath(), ComputerCraft.class.getClassLoader() );
|
||||
mounts.add( new FileSystemMount( fs, subPath ) );
|
||||
IMount jarMount = new JarMount( modJar, subPath );
|
||||
mounts.add( jarMount );
|
||||
}
|
||||
catch( IOException | RuntimeException | ServiceConfigurationError e )
|
||||
catch( IOException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Could not load mount from mod jar", e );
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
@@ -940,16 +936,16 @@ public class ComputerCraft
|
||||
if( resourcePackDir.exists() && resourcePackDir.isDirectory() )
|
||||
{
|
||||
String[] resourcePacks = resourcePackDir.list();
|
||||
for( String resourcePackName : resourcePacks )
|
||||
for( String resourcePack1 : resourcePacks )
|
||||
{
|
||||
try
|
||||
{
|
||||
File resourcePack = new File( resourcePackDir, resourcePackName );
|
||||
File resourcePack = new File( resourcePackDir, resourcePack1 );
|
||||
if( !resourcePack.isDirectory() )
|
||||
{
|
||||
// Mount a resource pack from a jar
|
||||
FileSystem fs = FileSystems.newFileSystem( resourcePack.toPath(), ComputerCraft.class.getClassLoader() );
|
||||
mounts.add( new FileSystemMount( fs, subPath ) );
|
||||
IMount resourcePackMount = new JarMount( resourcePack, subPath );
|
||||
mounts.add( resourcePackMount );
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,8 +13,6 @@ import net.minecraft.world.World;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
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".
|
||||
* @return A stream representing the contents of the file.
|
||||
* @throws IOException If the file does not exist, or could not be opened.
|
||||
* @deprecated Use {@link #openChannelForRead(String)} instead
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
@@ -13,8 +13,6 @@ import net.minecraft.world.World;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
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)}
|
||||
@@ -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".
|
||||
* @return A stream for writing to
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Use {@link #openChannelForWrite(String)} instead.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Use {@link #openChannelForAppend(String)} instead.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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
|
||||
* mount, and write operations should fail once it reaches zero.
|
||||
|
31
src/main/java/dan200/computercraft/api/lua/ICallContext.java
Normal file
31
src/main/java/dan200/computercraft/api/lua/ICallContext.java
Normal 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;
|
||||
}
|
31
src/main/java/dan200/computercraft/api/lua/ILuaCallable.java
Normal file
31
src/main/java/dan200/computercraft/api/lua/ILuaCallable.java
Normal 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;
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -13,8 +13,11 @@ import javax.annotation.Nullable;
|
||||
* 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
|
||||
* 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
|
||||
@@ -30,8 +33,10 @@ public interface ILuaContext
|
||||
* @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
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @deprecated Use {@link MethodResult#pullEvent(String, ILuaFunction)}
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see #pullEvent(String)
|
||||
* @deprecated Use {@link MethodResult#pullEventRaw(String, ILuaFunction)}
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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
|
||||
* intercepted, or the computer will leak memory and end up in a broken state.
|
||||
* @see #pullEvent(String)
|
||||
* @deprecated Use {@link MethodResult#pullEventRaw(ILuaFunction)}
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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,
|
||||
* 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.
|
||||
* @deprecated Use {@link MethodResult#onMainThread(ILuaCallable)}
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
33
src/main/java/dan200/computercraft/api/lua/ILuaFunction.java
Normal file
33
src/main/java/dan200/computercraft/api/lua/ILuaFunction.java
Normal 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;
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -41,16 +41,45 @@ public interface ILuaObject
|
||||
* 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, ILuaContext, int, Object[])}
|
||||
* the possible values and conversion rules.
|
||||
* @return An array of objects, representing the values you wish to return to the Lua program.
|
||||
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
|
||||
* conversion rules.
|
||||
* @throws LuaException If the task could not be queued, or if the task threw an exception.
|
||||
* for the possible values and conversion rules.
|
||||
* @return An array of objects, representing the values you wish to return to the Lua program. See
|
||||
* {@link MethodResult#of(Object...)} for the valid values and conversion rules.
|
||||
* @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.
|
||||
* @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
|
||||
* 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[])
|
||||
* @deprecated Use {@link #callMethod(ICallContext, int, Object[])} instead.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
354
src/main/java/dan200/computercraft/api/lua/MethodResult.java
Normal file
354
src/main/java/dan200/computercraft/api/lua/MethodResult.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,15 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dan200.computercraft.api.peripheral;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -70,10 +72,46 @@ public interface IPeripheral
|
||||
* 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.
|
||||
* @see #getMethodNames
|
||||
* @deprecated Use {@link #callMethod(IComputerAccess, ICallContext, int, Object[])} instead.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
|
@@ -9,6 +9,7 @@ package dan200.computercraft.api.turtle;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
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.
|
||||
* @see ITurtleCommand
|
||||
* @see ILuaContext#pullEvent(String)
|
||||
* @deprecated Use {@link #executeCommand(ITurtleCommand)} instead.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
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
|
||||
* it is finished.
|
||||
|
@@ -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
|
||||
* a transformation of {@code null} has the same effect as the identify matrix.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
@SideOnly( Side.CLIENT )
|
||||
@Nonnull
|
||||
Pair<IBakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );
|
||||
|
||||
|
@@ -8,7 +8,11 @@ package dan200.computercraft.client.proxy;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
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.computer.blocks.ComputerState;
|
||||
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.entity.TurtleVisionCamera;
|
||||
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.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
@@ -71,7 +75,7 @@ import java.util.List;
|
||||
|
||||
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
{
|
||||
private static Int2IntOpenHashMap lastCounts = new Int2IntOpenHashMap();
|
||||
private static TIntIntHashMap lastCounts = new TIntIntHashMap();
|
||||
|
||||
private long m_tick;
|
||||
private long m_renderFrame;
|
||||
|
@@ -18,14 +18,12 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.block.model.*;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
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 javax.annotation.Nonnull;
|
||||
@@ -34,9 +32,8 @@ import javax.vecmath.Matrix4f;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
|
||||
@@ -158,9 +155,9 @@ public class TurtleSmartItemModel implements IBakedModel, ISelectiveResourceRelo
|
||||
}
|
||||
|
||||
@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 )
|
||||
|
@@ -6,13 +6,12 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
|
||||
@@ -41,27 +40,28 @@ public class BufferAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// len
|
||||
return new Object[] { m_buffer.length() };
|
||||
return MethodResult.of( m_buffer.length() );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// tostring
|
||||
return new Object[] { m_buffer.toString() };
|
||||
return MethodResult.of( m_buffer.toString() );
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// read
|
||||
int start = optInt( arguments, 0, 0 );
|
||||
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:
|
||||
{
|
||||
@@ -70,7 +70,7 @@ public class BufferAPI implements ILuaAPI
|
||||
int start = optInt( arguments, 1, 0 );
|
||||
int end = optInt( arguments, 2, start + text.length() );
|
||||
m_buffer.write( text, start, end );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
@@ -79,14 +79,22 @@ public class BufferAPI implements ILuaAPI
|
||||
int start = optInt( arguments, 1, 0 );
|
||||
int end = optInt( arguments, 2, m_buffer.length() );
|
||||
m_buffer.fill( text, start, end );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
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 )
|
||||
@@ -110,8 +118,9 @@ public class BufferAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -124,12 +133,20 @@ public class BufferAPI implements ILuaAPI
|
||||
throw ArgumentHelper.badArgument( 1, "positive number", Integer.toString( repetitions ) );
|
||||
}
|
||||
TextBuffer buffer = new TextBuffer( text, repetitions );
|
||||
return new Object[] { new BufferLuaObject( buffer ) };
|
||||
return MethodResult.of( new BufferLuaObject( buffer ) );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@@ -6,26 +6,22 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryOutputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.filesystem.FileSystemWrapper;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
|
||||
|
||||
@@ -84,8 +80,9 @@ public class FSAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -100,7 +97,7 @@ public class FSAPI implements ILuaAPI
|
||||
for(int i=0; i<results.length; ++i ) {
|
||||
table.put( i+1, results[i] );
|
||||
}
|
||||
return new Object[] { table };
|
||||
return MethodResult.of( table );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
@@ -112,13 +109,13 @@ public class FSAPI implements ILuaAPI
|
||||
// combine
|
||||
String pathA = getString( args, 0 );
|
||||
String pathB = getString( args, 1 );
|
||||
return new Object[] { m_fileSystem.combine( pathA, pathB ) };
|
||||
return MethodResult.of( m_fileSystem.combine( pathA, pathB ) );
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// getName
|
||||
String path = getString( args, 0 );
|
||||
return new Object[]{ FileSystem.getName( path ) };
|
||||
return MethodResult.of( FileSystem.getName( path ) );
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
@@ -126,7 +123,7 @@ public class FSAPI implements ILuaAPI
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
return new Object[]{ m_fileSystem.getSize( path ) };
|
||||
return MethodResult.of( m_fileSystem.getSize( path ) );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
@@ -138,9 +135,9 @@ public class FSAPI implements ILuaAPI
|
||||
// exists
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
return new Object[]{ m_fileSystem.exists( path ) };
|
||||
return MethodResult.of( m_fileSystem.exists( path ) );
|
||||
} catch( FileSystemException e ) {
|
||||
return new Object[]{ false };
|
||||
return MethodResult.of( false );
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
@@ -148,9 +145,9 @@ public class FSAPI implements ILuaAPI
|
||||
// isDir
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
return new Object[]{ m_fileSystem.isDir( path ) };
|
||||
return MethodResult.of( m_fileSystem.isDir( path ) );
|
||||
} catch( FileSystemException e ) {
|
||||
return new Object[]{ false };
|
||||
return MethodResult.of( false );
|
||||
}
|
||||
}
|
||||
case 6:
|
||||
@@ -158,9 +155,9 @@ public class FSAPI implements ILuaAPI
|
||||
// isReadOnly
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
return new Object[]{ m_fileSystem.isReadOnly( path ) };
|
||||
return MethodResult.of( m_fileSystem.isReadOnly( path ) );
|
||||
} catch( FileSystemException e ) {
|
||||
return new Object[]{ false };
|
||||
return MethodResult.of( false );
|
||||
}
|
||||
}
|
||||
case 7:
|
||||
@@ -170,7 +167,7 @@ public class FSAPI implements ILuaAPI
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.makeDir( path );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -183,7 +180,7 @@ public class FSAPI implements ILuaAPI
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.move( path, dest );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -196,7 +193,7 @@ public class FSAPI implements ILuaAPI
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.copy( path, dest );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -208,7 +205,7 @@ public class FSAPI implements ILuaAPI
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.delete( path );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -225,44 +222,44 @@ public class FSAPI implements ILuaAPI
|
||||
case "r":
|
||||
{
|
||||
// Open the file for reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
|
||||
InputStream reader = m_fileSystem.openForRead( path );
|
||||
return MethodResult.of( new EncodedInputHandle( reader ) );
|
||||
}
|
||||
case "w":
|
||||
{
|
||||
// Open the file for writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, false );
|
||||
return MethodResult.of( new EncodedOutputHandle( writer ) );
|
||||
}
|
||||
case "a":
|
||||
{
|
||||
// Open the file for appending, then create a wrapper around the writer
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, true );
|
||||
return MethodResult.of( new EncodedOutputHandle( writer ) );
|
||||
}
|
||||
case "rb":
|
||||
{
|
||||
// Open the file for binary reading, then create a wrapper around the reader
|
||||
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
|
||||
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
|
||||
InputStream reader = m_fileSystem.openForRead( path );
|
||||
return MethodResult.of( new BinaryInputHandle( reader ) );
|
||||
}
|
||||
case "wb":
|
||||
{
|
||||
// Open the file for binary writing, then create a wrapper around the writer
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, false );
|
||||
return MethodResult.of( new BinaryOutputHandle( writer ) );
|
||||
}
|
||||
case "ab":
|
||||
{
|
||||
// Open the file for binary appending, then create a wrapper around the reader
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, true );
|
||||
return MethodResult.of( new BinaryOutputHandle( writer ) );
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported mode" );
|
||||
}
|
||||
} catch( FileSystemException e ) {
|
||||
return new Object[] { null, e.getMessage() };
|
||||
return MethodResult.of( null, e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 12:
|
||||
@@ -272,9 +269,9 @@ public class FSAPI implements ILuaAPI
|
||||
try {
|
||||
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 ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -287,9 +284,9 @@ public class FSAPI implements ILuaAPI
|
||||
long freeSpace = m_fileSystem.getFreeSpace( path );
|
||||
if( freeSpace >= 0 )
|
||||
{
|
||||
return new Object[]{ freeSpace };
|
||||
return MethodResult.of( freeSpace );
|
||||
}
|
||||
return new Object[]{ "unlimited" };
|
||||
return MethodResult.of( "unlimited" );
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -305,7 +302,7 @@ public class FSAPI implements ILuaAPI
|
||||
for(int i=0; i<results.length; ++i ) {
|
||||
table.put( i+1, results[i] );
|
||||
}
|
||||
return new Object[] { table };
|
||||
return MethodResult.of( table );
|
||||
} catch( FileSystemException e ) {
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
@@ -314,13 +311,21 @@ public class FSAPI implements ILuaAPI
|
||||
{
|
||||
// getDir
|
||||
String path = getString( args, 0 );
|
||||
return new Object[]{ FileSystem.getDirectory( path ) };
|
||||
return MethodResult.of( FileSystem.getDirectory( path ) );
|
||||
}
|
||||
default:
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@@ -8,12 +8,12 @@ package dan200.computercraft.core.apis;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -102,8 +102,9 @@ public class HTTPAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -164,11 +165,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
|
||||
}
|
||||
return new Object[]{ true };
|
||||
return MethodResult.of(true);
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
return MethodResult.of( false, e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
@@ -186,11 +187,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
|
||||
}
|
||||
return new Object[]{ true };
|
||||
return MethodResult.of( true );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
return MethodResult.of( false, e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 2: // websocket
|
||||
@@ -223,20 +224,28 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
m_httpTasks.add( connector );
|
||||
}
|
||||
return new Object[]{ true };
|
||||
return MethodResult.of(true);
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
return MethodResult.of( false, e.getMessage() );
|
||||
}
|
||||
}
|
||||
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 )
|
||||
{
|
||||
synchronized( m_closeables )
|
||||
|
@@ -6,12 +6,12 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
@@ -221,7 +221,8 @@ public class OSAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@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 )
|
||||
{
|
||||
@@ -229,7 +230,7 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// queueEvent
|
||||
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -238,7 +239,7 @@ public class OSAPI implements ILuaAPI
|
||||
synchronized( m_timers )
|
||||
{
|
||||
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:
|
||||
@@ -253,33 +254,33 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
int day = (time > m_time) ? m_day : (m_day + 1);
|
||||
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
|
||||
return new Object[] { m_nextAlarmToken++ };
|
||||
return MethodResult.of( m_nextAlarmToken++ );
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// shutdown
|
||||
m_apiEnvironment.shutdown();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// reboot
|
||||
m_apiEnvironment.reboot();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 5:
|
||||
case 6:
|
||||
{
|
||||
// computerID/getComputerID
|
||||
return new Object[] { getComputerID() };
|
||||
return MethodResult.of( getComputerID() );
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
// setComputerLabel
|
||||
String label = optString( args, 0, null );
|
||||
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 8:
|
||||
case 9:
|
||||
@@ -288,16 +289,16 @@ public class OSAPI implements ILuaAPI
|
||||
String label = m_apiEnvironment.getLabel();
|
||||
if( label != null )
|
||||
{
|
||||
return new Object[] { label };
|
||||
return MethodResult.of( label );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 10:
|
||||
{
|
||||
// clock
|
||||
synchronized( m_timers )
|
||||
{
|
||||
return new Object[] { m_clock * 0.05 };
|
||||
return MethodResult.of( m_clock * 0.05 );
|
||||
}
|
||||
}
|
||||
case 11:
|
||||
@@ -310,19 +311,19 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// Get Hour of day (UTC)
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getTimeForCalendar( c ) };
|
||||
return MethodResult.of( getTimeForCalendar( c ) );
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get Hour of day (local time)
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getTimeForCalendar( c ) };
|
||||
return MethodResult.of( getTimeForCalendar( c ) );
|
||||
}
|
||||
case "ingame":
|
||||
// Get ingame hour
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] { m_time };
|
||||
return MethodResult.of( m_time );
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
@@ -338,19 +339,19 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// Get numbers of days since 1970-01-01 (utc)
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getDayForCalendar( c ) };
|
||||
return MethodResult.of( getDayForCalendar( c ) );
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get numbers of days since 1970-01-01 (local time)
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getDayForCalendar( c ) };
|
||||
return MethodResult.of( getDayForCalendar( c ) );
|
||||
}
|
||||
case "ingame":
|
||||
// Get game day
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] { m_day };
|
||||
return MethodResult.of( m_day );
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
@@ -367,7 +368,7 @@ public class OSAPI implements ILuaAPI
|
||||
m_timers.remove( token );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 14:
|
||||
{
|
||||
@@ -380,7 +381,7 @@ public class OSAPI implements ILuaAPI
|
||||
m_alarms.remove( token );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 15:
|
||||
{
|
||||
@@ -392,21 +393,21 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// Get utc epoch
|
||||
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
|
||||
return new Object[] { getEpochForCalendar( c ) };
|
||||
return MethodResult.of( getEpochForCalendar( c ) );
|
||||
}
|
||||
case "local":
|
||||
{
|
||||
// Get local epoch
|
||||
Calendar c = Calendar.getInstance();
|
||||
return new Object[] { getEpochForCalendar( c ) };
|
||||
return MethodResult.of( getEpochForCalendar( c ) );
|
||||
}
|
||||
case "ingame":
|
||||
// Get in-game epoch
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
m_day * 86400000 + (int) (m_time * 3600000.0f)
|
||||
};
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported operation" );
|
||||
@@ -414,11 +415,18 @@ public class OSAPI implements ILuaAPI
|
||||
}
|
||||
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 void queueLuaEvent( String event, Object[] args )
|
||||
|
@@ -8,9 +8,8 @@ package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
@@ -98,7 +97,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
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;
|
||||
synchronized( this )
|
||||
@@ -368,8 +367,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -389,7 +389,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Object[] { present };
|
||||
return MethodResult.of( present );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -408,10 +408,10 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
if( type != null )
|
||||
{
|
||||
return new Object[] { type };
|
||||
return MethodResult.of( type );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
@@ -435,9 +435,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
for(int i=0; i<methods.length; ++i ) {
|
||||
table.put( i+1, methods[i] );
|
||||
}
|
||||
return new Object[] { table };
|
||||
return MethodResult.of( table );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
@@ -462,11 +462,19 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
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
|
||||
|
||||
private int parseSide( Object[] args ) throws LuaException
|
||||
|
@@ -6,12 +6,12 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -56,8 +56,9 @@ public class RedstoneAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -69,7 +70,7 @@ public class RedstoneAPI implements ILuaAPI
|
||||
{
|
||||
table.put( i+1, Computer.s_sideNames[i] );
|
||||
}
|
||||
return new Object[] { table };
|
||||
return MethodResult.of( table );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -77,19 +78,19 @@ public class RedstoneAPI implements ILuaAPI
|
||||
int side = parseSide( args );
|
||||
boolean output = getBoolean( args, 1 );
|
||||
m_environment.setOutput( side, output ? 15 : 0 );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// getOutput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getOutput( side ) > 0 };
|
||||
return MethodResult.of( m_environment.getOutput( side ) > 0 );
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// getInput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getInput( side ) > 0 };
|
||||
return MethodResult.of( m_environment.getInput( side ) > 0 );
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
@@ -97,19 +98,19 @@ public class RedstoneAPI implements ILuaAPI
|
||||
int side = parseSide( args );
|
||||
int output = getInt( args, 1 );
|
||||
m_environment.setBundledOutput( side, output );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// getBundledOutput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getBundledOutput( side ) };
|
||||
return MethodResult.of( m_environment.getBundledOutput( side ) );
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// getBundledInput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getBundledInput( side ) };
|
||||
return MethodResult.of( m_environment.getBundledInput( side ) );
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
@@ -117,7 +118,7 @@ public class RedstoneAPI implements ILuaAPI
|
||||
int side = parseSide( args );
|
||||
int mask = getInt( args, 1 );
|
||||
int input = m_environment.getBundledInput( side );
|
||||
return new Object[] { ((input & mask) == mask) };
|
||||
return MethodResult.of( ((input & mask) == mask) );
|
||||
}
|
||||
case 8:
|
||||
case 9:
|
||||
@@ -130,28 +131,36 @@ public class RedstoneAPI implements ILuaAPI
|
||||
throw new LuaException( "Expected number in range 0-15" );
|
||||
}
|
||||
m_environment.setOutput( side, output );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 10:
|
||||
case 11:
|
||||
{
|
||||
// getAnalogOutput/getAnalogueOutput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getOutput( side ) };
|
||||
return MethodResult.of( m_environment.getOutput( side ) );
|
||||
}
|
||||
case 12:
|
||||
case 13:
|
||||
{
|
||||
// getAnalogInput/getAnalogueInput
|
||||
int side = parseSide( args );
|
||||
return new Object[] { m_environment.getInput( side ) };
|
||||
return MethodResult.of( m_environment.getInput( side ) );
|
||||
}
|
||||
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
|
||||
{
|
||||
|
@@ -6,15 +6,15 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
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.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
|
||||
@@ -64,8 +64,7 @@ public class TermAPI implements ILuaAPI
|
||||
"setPaletteColour",
|
||||
"setPaletteColor",
|
||||
"getPaletteColour",
|
||||
"getPaletteColor",
|
||||
"getCursorBlink",
|
||||
"getPaletteColor"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,11 +83,9 @@ public class TermAPI implements ILuaAPI
|
||||
return colour;
|
||||
}
|
||||
|
||||
public static Object[] encodeColour( int colour ) throws LuaException
|
||||
public static MethodResult encodeColour( int colour )
|
||||
{
|
||||
return new Object[] {
|
||||
1 << colour
|
||||
};
|
||||
return MethodResult.of( 1 << colour );
|
||||
}
|
||||
|
||||
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
|
||||
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 )
|
||||
{
|
||||
@@ -120,7 +118,7 @@ public class TermAPI implements ILuaAPI
|
||||
m_terminal.write( text );
|
||||
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -130,7 +128,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.scroll(y);
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
@@ -141,7 +139,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.setCursorPos( x, y );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
@@ -151,7 +149,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.setCursorBlink( b );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
@@ -162,7 +160,7 @@ public class TermAPI implements ILuaAPI
|
||||
x = m_terminal.getCursorX();
|
||||
y = m_terminal.getCursorY();
|
||||
}
|
||||
return new Object[] { x + 1, y + 1 };
|
||||
return MethodResult.of( x + 1, y + 1 );
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
@@ -173,7 +171,7 @@ public class TermAPI implements ILuaAPI
|
||||
width = m_terminal.getWidth();
|
||||
height = m_terminal.getHeight();
|
||||
}
|
||||
return new Object[] { width, height };
|
||||
return MethodResult.of( width, height );
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
@@ -182,7 +180,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.clear();
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
@@ -191,7 +189,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.clearLine();
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 8:
|
||||
case 9:
|
||||
@@ -202,7 +200,7 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.setTextColour( colour );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 10:
|
||||
case 11:
|
||||
@@ -213,13 +211,13 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
m_terminal.setBackgroundColour( colour );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 12:
|
||||
case 13:
|
||||
{
|
||||
// isColour/isColor
|
||||
return new Object[] { m_environment.isColour() };
|
||||
return MethodResult.of( m_environment.isColour() );
|
||||
}
|
||||
case 14:
|
||||
case 15:
|
||||
@@ -249,7 +247,7 @@ public class TermAPI implements ILuaAPI
|
||||
m_terminal.blit( text, textColour, backgroundColour );
|
||||
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 19:
|
||||
case 20:
|
||||
@@ -269,7 +267,7 @@ public class TermAPI implements ILuaAPI
|
||||
double b = getReal( args, 3 );
|
||||
setColour( m_terminal, colour, r, g, b );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 21:
|
||||
case 22:
|
||||
@@ -280,21 +278,26 @@ public class TermAPI implements ILuaAPI
|
||||
{
|
||||
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:
|
||||
{
|
||||
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 )
|
||||
{
|
||||
int bit = 0;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,43 +1,55 @@
|
||||
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.MethodResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
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 java.io.*;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
|
||||
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 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;
|
||||
}
|
||||
|
||||
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
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
{
|
||||
return new String[] {
|
||||
"readLine",
|
||||
"readAll",
|
||||
@@ -46,35 +58,31 @@ public class EncodedReadableHandle extends HandleGeneric
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// readLine
|
||||
checkOpen();
|
||||
boolean withTrailing = optBoolean( args, 0, false );
|
||||
try
|
||||
{
|
||||
String line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
// While this is technically inaccurate, it's better than nothing
|
||||
if( withTrailing ) line += "\n";
|
||||
return new Object[] { line };
|
||||
return MethodResult.of( line );
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
// readAll
|
||||
checkOpen();
|
||||
@@ -91,17 +99,18 @@ public class EncodedReadableHandle extends HandleGeneric
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
return new Object[] { result.toString() };
|
||||
return MethodResult.of( result.toString() );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
// close
|
||||
close();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
case 3:
|
||||
// read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
@@ -118,7 +127,7 @@ public class EncodedReadableHandle extends HandleGeneric
|
||||
char[] chars = new char[ count ];
|
||||
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
|
||||
{
|
||||
@@ -143,30 +152,15 @@ public class EncodedReadableHandle extends HandleGeneric
|
||||
out.append( buffer, 0, read );
|
||||
}
|
||||
|
||||
return new Object[] { out.toString() };
|
||||
return MethodResult.of( out.toString() );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
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 ) );
|
||||
}
|
||||
}
|
@@ -1,32 +1,45 @@
|
||||
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.MethodResult;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedWriter;
|
||||
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;
|
||||
import java.io.*;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -41,8 +54,9 @@ public class EncodedWritableHandle extends HandleGeneric
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -62,7 +76,7 @@ public class EncodedWritableHandle extends HandleGeneric
|
||||
try
|
||||
{
|
||||
m_writer.write( text, 0, text.length() );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
@@ -86,7 +100,7 @@ public class EncodedWritableHandle extends HandleGeneric
|
||||
{
|
||||
m_writer.write( text, 0, text.length() );
|
||||
m_writer.newLine();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
@@ -99,34 +113,18 @@ public class EncodedWritableHandle extends HandleGeneric
|
||||
try
|
||||
{
|
||||
m_writer.flush();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
// close
|
||||
close();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
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 ) );
|
||||
}
|
||||
}
|
@@ -1,25 +1,23 @@
|
||||
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.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
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
|
||||
{
|
||||
private Closeable m_closable;
|
||||
private boolean m_open = true;
|
||||
protected final Closeable m_closable;
|
||||
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
|
||||
@@ -27,7 +25,7 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
if( !m_open ) throw new LuaException( "attempt to use a closed file" );
|
||||
}
|
||||
|
||||
protected final void close()
|
||||
protected void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -37,64 +35,13 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
catch( IOException ignored )
|
||||
{
|
||||
}
|
||||
m_closable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared implementation for various file handle types
|
||||
*
|
||||
* @param channel The channel to seek in
|
||||
* @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
|
||||
@Nullable
|
||||
@Override
|
||||
@Deprecated
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
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.nio.NioEventLoopGroup;
|
||||
|
||||
@@ -24,14 +24,18 @@ public final class HTTPExecutor
|
||||
public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor(
|
||||
4, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
ThreadUtils.builder( "HTTP" )
|
||||
new SynchronousQueue<Runnable>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon( true )
|
||||
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
|
||||
.setNameFormat( "ComputerCraft-HTTP-%d" )
|
||||
.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 )
|
||||
.setNameFormat( "ComputerCraft-Netty-%d" )
|
||||
.build()
|
||||
);
|
||||
|
||||
|
@@ -9,21 +9,16 @@ package dan200.computercraft.core.apis.http;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
@@ -186,10 +181,6 @@ public class HTTPRequest implements Runnable
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
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.
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
@@ -201,11 +192,9 @@ public class HTTPRequest implements Runnable
|
||||
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
|
||||
getHeaderSize( connection.getHeaderFields() ) + result.length );
|
||||
|
||||
SeekableByteChannel contents = new ArrayByteChannel( result );
|
||||
InputStream contents = new ByteArrayInputStream( result );
|
||||
ILuaObject stream = wrapStream(
|
||||
m_binary
|
||||
? new BinaryReadableHandle( contents )
|
||||
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, charset ) ),
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
|
||||
connection.getResponseCode(), headers
|
||||
);
|
||||
|
||||
@@ -246,8 +235,9 @@ public class HTTPRequest implements Runnable
|
||||
return newMethods;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -258,19 +248,27 @@ public class HTTPRequest implements Runnable
|
||||
case 0:
|
||||
{
|
||||
// getResponseCode
|
||||
return new Object[]{ responseCode };
|
||||
return MethodResult.of( responseCode );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// getResponseHeaders
|
||||
return new Object[]{ responseHeaders };
|
||||
return MethodResult.of( responseHeaders );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -7,15 +7,11 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -28,8 +24,6 @@ import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
|
||||
|
||||
public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable
|
||||
{
|
||||
public static final String SUCCESS_EVENT = "websocket_success";
|
||||
@@ -160,36 +154,48 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
|
||||
@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 ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
while( true )
|
||||
checkOpen();
|
||||
return MethodResult.pullEvent( MESSAGE_EVENT, new ILuaFunction()
|
||||
{
|
||||
checkOpen();
|
||||
Object[] event = context.pullEvent( MESSAGE_EVENT );
|
||||
if( event.length >= 3 && Objects.equal( event[1], url ) )
|
||||
@Nonnull
|
||||
@Override
|
||||
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:
|
||||
{
|
||||
checkOpen();
|
||||
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
|
||||
boolean binary = optBoolean(arguments, 1, false);
|
||||
computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
|
||||
channel.writeAndFlush( binary
|
||||
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
|
||||
: new TextWebSocketFrame( text ) );
|
||||
return null;
|
||||
channel.writeAndFlush( new TextWebSocketFrame( text ) );
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
close( true );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
default:
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -265,10 +265,18 @@ public class Computer
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@Deprecated
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
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;
|
||||
@@ -960,7 +968,7 @@ public class Computer
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final Computer computer = this;
|
||||
ITask task = new ITask() {
|
||||
@Override
|
||||
|
@@ -8,14 +8,13 @@ package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.shared.util.ThreadUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ComputerThread
|
||||
{
|
||||
@@ -57,8 +56,8 @@ public class ComputerThread
|
||||
*/
|
||||
private static Thread[] s_threads = null;
|
||||
|
||||
private static final ThreadFactory s_ManagerFactory = ThreadUtils.factory( "Computer-Manager" );
|
||||
private static final ThreadFactory s_RunnerFactory = ThreadUtils.factory( "Computer-Runner" );
|
||||
private static final AtomicInteger s_ManagerCounter = new AtomicInteger( 1 );
|
||||
private static final AtomicInteger s_DelegateCounter = new AtomicInteger( 1 );
|
||||
|
||||
/**
|
||||
* Start the computer thread
|
||||
@@ -73,12 +72,16 @@ public class ComputerThread
|
||||
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++ )
|
||||
{
|
||||
Thread thread = s_threads[i];
|
||||
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() )
|
||||
{
|
||||
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();
|
||||
|
@@ -11,7 +11,6 @@ import dan200.computercraft.api.filesystem.IMount;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -115,7 +114,6 @@ public class ComboMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
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" );
|
||||
}
|
||||
|
||||
@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" );
|
||||
}
|
||||
}
|
||||
|
@@ -9,36 +9,34 @@ package dan200.computercraft.core.filesystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyMount implements IMount
|
||||
{
|
||||
{
|
||||
public EmptyMount()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// IMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path )
|
||||
{
|
||||
@@ -47,17 +45,8 @@ public class EmptyMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
public InputStream openForRead( @Nonnull String path )
|
||||
{
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -6,54 +6,68 @@
|
||||
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
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.Set;
|
||||
|
||||
public class FileMount implements IWritableMount
|
||||
{
|
||||
private static final 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 static final Set<OpenOption> APPEND_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND );
|
||||
|
||||
private class WritableCountingChannel implements WritableByteChannel
|
||||
private static int MINIMUM_FILE_SIZE = 500;
|
||||
|
||||
private class CountingOutputStream extends OutputStream
|
||||
{
|
||||
|
||||
private final WritableByteChannel m_inner;
|
||||
long m_ignoredBytesLeft;
|
||||
|
||||
WritableCountingChannel( WritableByteChannel inner, long bytesToIgnore )
|
||||
private OutputStream m_innerStream;
|
||||
private long m_ignoredBytesLeft;
|
||||
|
||||
public CountingOutputStream( OutputStream innerStream, long bytesToIgnore )
|
||||
{
|
||||
m_inner = inner;
|
||||
m_innerStream = innerStream;
|
||||
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
|
||||
public int write( @Nonnull ByteBuffer b ) throws IOException
|
||||
public void write( int b ) throws IOException
|
||||
{
|
||||
count( b.remaining() );
|
||||
return m_inner.write( b );
|
||||
count( 1 );
|
||||
m_innerStream.write( b );
|
||||
}
|
||||
|
||||
void count( long n ) throws IOException
|
||||
private void count( long n ) throws IOException
|
||||
{
|
||||
m_ignoredBytesLeft -= n;
|
||||
if( m_ignoredBytesLeft < 0 )
|
||||
{
|
||||
long newBytes = -m_ignoredBytesLeft;
|
||||
m_ignoredBytesLeft = 0;
|
||||
|
||||
|
||||
long bytesLeft = m_capacity - m_usedSpace;
|
||||
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 long m_capacity;
|
||||
private long m_usedSpace;
|
||||
|
||||
|
||||
public FileMount( File rootPath, long capacity )
|
||||
{
|
||||
m_rootPath = rootPath;
|
||||
@@ -146,7 +93,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
|
||||
// IMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
@@ -160,7 +107,7 @@ public class FileMount implements IWritableMount
|
||||
return file.exists();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
@@ -174,7 +121,7 @@ public class FileMount implements IWritableMount
|
||||
return file.exists() && file.isDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,10 +180,9 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
@@ -247,26 +193,11 @@ public class FileMount implements IWritableMount
|
||||
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
|
||||
|
||||
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
@@ -293,7 +224,7 @@ public class FileMount implements IWritableMount
|
||||
{
|
||||
throw new IOException( "/" + path + ": Out of space" );
|
||||
}
|
||||
|
||||
|
||||
boolean success = file.mkdirs();
|
||||
if( success )
|
||||
{
|
||||
@@ -305,7 +236,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete( @Nonnull String path ) throws IOException
|
||||
{
|
||||
@@ -313,7 +244,7 @@ public class FileMount implements IWritableMount
|
||||
{
|
||||
throw new IOException( "/" + path + ": Access denied" );
|
||||
}
|
||||
|
||||
|
||||
if( created() )
|
||||
{
|
||||
File file = getRealPath( path );
|
||||
@@ -323,7 +254,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void deleteRecursively( File file ) throws IOException
|
||||
{
|
||||
// Empty directories first
|
||||
@@ -335,7 +266,7 @@ public class FileMount implements IWritableMount
|
||||
deleteRecursively( new File( file, aChildren ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Then delete
|
||||
long fileSize = file.isDirectory() ? 0 : file.length();
|
||||
boolean success = file.delete();
|
||||
@@ -348,26 +279,10 @@ public class FileMount implements IWritableMount
|
||||
throw new IOException( "Access denied" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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 ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
create();
|
||||
File file = getRealPath( path );
|
||||
@@ -393,13 +308,13 @@ public class FileMount implements IWritableMount
|
||||
m_usedSpace -= Math.max( file.length(), 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
|
||||
@Override
|
||||
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
{
|
||||
@@ -414,11 +329,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allowing seeking when appending is not recommended, so we use a separate channel.
|
||||
return new WritableCountingChannel(
|
||||
Files.newByteChannel( file.toPath(), APPEND_OPTIONS ),
|
||||
Math.max( MINIMUM_FILE_SIZE - file.length(), 0 )
|
||||
);
|
||||
return new CountingOutputStream( new FileOutputStream( file, true ), Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -426,23 +337,23 @@ public class FileMount implements IWritableMount
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace()
|
||||
{
|
||||
return Math.max( m_capacity - m_usedSpace, 0 );
|
||||
}
|
||||
|
||||
|
||||
public File getRealPath( String path )
|
||||
{
|
||||
return new File( m_rootPath, path );
|
||||
}
|
||||
|
||||
|
||||
private boolean created()
|
||||
{
|
||||
return m_rootPath.exists();
|
||||
}
|
||||
|
||||
|
||||
private void create() throws IOException
|
||||
{
|
||||
if( !m_rootPath.exists() )
|
||||
@@ -454,7 +365,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private long measureUsedSpace( File file )
|
||||
{
|
||||
if( !file.exists() )
|
||||
|
@@ -6,23 +6,13 @@
|
||||
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
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.io.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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 );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
|
||||
{
|
||||
return m_mount.openChannelForRead( path );
|
||||
return m_mount.openForRead( path );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -219,17 +209,13 @@ public class FileSystem
|
||||
m_writableMount.delete( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForWrite( String path ) throws FileSystemException
|
||||
public OutputStream openForWrite( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null )
|
||||
{
|
||||
@@ -252,20 +238,16 @@ public class FileSystem
|
||||
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 )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForAppend( String path ) throws FileSystemException
|
||||
public OutputStream openForAppend( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null )
|
||||
{
|
||||
@@ -284,7 +266,7 @@ public class FileSystem
|
||||
m_writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return m_writableMount.openChannelForWrite( path );
|
||||
return m_writableMount.openForWrite( path );
|
||||
}
|
||||
else if( m_mount.isDirectory( path ) )
|
||||
{
|
||||
@@ -292,13 +274,9 @@ public class FileSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_writableMount.openChannelForAppend( path );
|
||||
return m_writableMount.openForAppend( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
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 HashMap<WeakReference<FileSystemWrapper<?>>, Closeable> m_openFiles = new HashMap<>();
|
||||
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
|
||||
|
||||
private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() );
|
||||
|
||||
public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException
|
||||
{
|
||||
mount( rootLabel, "", rootMount );
|
||||
@@ -334,15 +310,24 @@ public class FileSystem
|
||||
// Close all dangling open files
|
||||
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();
|
||||
while( m_openFileQueue.poll() != null ) ;
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
if( location.contains( ".." ) ) {
|
||||
throw new FileSystemException( "Cannot mount below the root" );
|
||||
@@ -363,18 +348,24 @@ public class FileSystem
|
||||
}
|
||||
mount( new MountWrapper( label, location, mount ) );
|
||||
}
|
||||
|
||||
|
||||
private synchronized void mount( MountWrapper wrapper )
|
||||
{
|
||||
String location = wrapper.getLocation();
|
||||
m_mounts.remove( location );
|
||||
if( m_mounts.containsKey( location ) )
|
||||
{
|
||||
m_mounts.remove( location );
|
||||
}
|
||||
m_mounts.put( location, wrapper );
|
||||
}
|
||||
|
||||
public synchronized void unmount( String 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 )
|
||||
@@ -608,85 +599,108 @@ public class FileSystem
|
||||
else
|
||||
{
|
||||
// Copy a file:
|
||||
try( ReadableByteChannel source = sourceMount.openForRead( sourcePath );
|
||||
WritableByteChannel destination = destinationMount.openForWrite( destinationPath ) )
|
||||
InputStream source = null;
|
||||
OutputStream destination = null;
|
||||
try
|
||||
{
|
||||
// Open both files
|
||||
source = sourceMount.openForRead( sourcePath );
|
||||
destination = destinationMount.openForWrite( destinationPath );
|
||||
|
||||
// Copy bytes as fast as we can
|
||||
ByteStreams.copy( source, destination );
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
byte[] buffer = new byte[1024];
|
||||
while( true )
|
||||
{
|
||||
int bytesRead = source.read( buffer );
|
||||
if( bytesRead >= 0 )
|
||||
{
|
||||
destination.write( buffer, 0, bytesRead );
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup()
|
||||
{
|
||||
synchronized( m_openFiles )
|
||||
{
|
||||
Reference<?> ref;
|
||||
while( (ref = m_openFileQueue.poll()) != null )
|
||||
finally
|
||||
{
|
||||
Closeable file = m_openFiles.remove( ref );
|
||||
if( file != null ) closeQuietly( file );
|
||||
// Close both files
|
||||
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 )
|
||||
{
|
||||
if( ComputerCraft.maximumFilesOpen > 0 &&
|
||||
m_openFiles.size() >= ComputerCraft.maximumFilesOpen )
|
||||
{
|
||||
closeQuietly( file );
|
||||
throw new FileSystemException( "Too many files already open" );
|
||||
if( handle != null )
|
||||
{
|
||||
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.put( wrapper.self, file );
|
||||
return wrapper;
|
||||
m_openFiles.add( handle );
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void removeFile( FileSystemWrapper<?> handle )
|
||||
private synchronized void closeFile( Closeable handle ) throws IOException
|
||||
{
|
||||
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 );
|
||||
ReadableByteChannel channel = mount.openForRead( path );
|
||||
if( channel != null )
|
||||
InputStream stream = mount.openForRead( path );
|
||||
if( stream != null )
|
||||
{
|
||||
return openFile( open.apply( channel ) );
|
||||
return openFile( new ClosingInputStream( stream ), stream );
|
||||
}
|
||||
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 );
|
||||
WritableByteChannel channel = append ? mount.openForAppend( path ) : mount.openForWrite( path );
|
||||
if( channel != null )
|
||||
OutputStream stream = append ? mount.openForAppend( path ) : mount.openForWrite( path );
|
||||
if( stream != null )
|
||||
{
|
||||
return openFile( open.apply( channel ) );
|
||||
return openFile( new ClosingOutputStream( stream ), stream );
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,132 +1,185 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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 );
|
||||
rootEntry = new Entry( "", rootPath );
|
||||
this.m_filesystem = m_filesystem;
|
||||
}
|
||||
|
||||
Queue<Entry> entries = new ArrayDeque<>();
|
||||
entries.add( rootEntry );
|
||||
while( !entries.isEmpty() )
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
Entry entry = entries.remove();
|
||||
try( Stream<Path> childStream = Files.list( entry.path ) )
|
||||
{
|
||||
Iterator<Path> children = childStream.iterator();
|
||||
while( children.hasNext() )
|
||||
{
|
||||
Path childPath = children.next();
|
||||
Entry child = new Entry( childPath.getFileName().toString(), childPath );
|
||||
entry.children.put( child.name, child );
|
||||
if( child.directory ) entries.add( child );
|
||||
}
|
||||
}
|
||||
m_filesystem.makeDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
public long getRemainingSpace() throws IOException
|
||||
{
|
||||
Entry entry = getFile( path );
|
||||
return entry != null && entry.directory;
|
||||
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
|
||||
{
|
||||
Entry entry = getFile( path );
|
||||
if( entry == null || !entry.directory ) throw new IOException( "/" + path + ": Not a directory" );
|
||||
|
||||
contents.addAll( entry.children.keySet() );
|
||||
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
|
||||
{
|
||||
Entry file = getFile( path );
|
||||
if( file == null ) throw new IOException( "/" + path + ": No such file" );
|
||||
return file.size;
|
||||
try
|
||||
{
|
||||
return m_filesystem.getSize( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
Entry file = getFile( path );
|
||||
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 )
|
||||
try
|
||||
{
|
||||
if( entry == null || entry.children == null ) return null;
|
||||
entry = entry.children.get( component );
|
||||
return m_filesystem.openForRead( path );
|
||||
}
|
||||
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;
|
||||
final Path path;
|
||||
return m_filesystem.combine( path, child );
|
||||
}
|
||||
|
||||
final boolean directory;
|
||||
final long size;
|
||||
final Map<String, Entry> children;
|
||||
|
||||
private Entry( String name, Path path ) throws IOException
|
||||
@Override
|
||||
public void copy( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
this.path = path;
|
||||
|
||||
BasicFileAttributes attributes = Files.readAttributes( path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS );
|
||||
this.directory = attributes.isDirectory();
|
||||
this.size = directory ? 0 : attributes.size();
|
||||
this.children = directory ? new HashMap<>() : null;
|
||||
@Override
|
||||
public void move( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.move( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,16 +20,15 @@ import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@Deprecated
|
||||
public class JarMount implements IMount
|
||||
{
|
||||
private static class FileInZip
|
||||
{
|
||||
private class FileInZip
|
||||
{
|
||||
private String m_path;
|
||||
private boolean m_directory;
|
||||
private long m_size;
|
||||
private Map<String, FileInZip> m_children;
|
||||
|
||||
|
||||
public FileInZip( String path, boolean directory, long size )
|
||||
{
|
||||
m_path = path;
|
||||
@@ -37,44 +36,44 @@ public class JarMount implements IMount
|
||||
m_size = m_directory ? 0 : size;
|
||||
m_children = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
|
||||
public String getPath()
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
|
||||
public boolean isDirectory()
|
||||
{
|
||||
return m_directory;
|
||||
}
|
||||
|
||||
|
||||
public long getSize()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
|
||||
public void list( List<String> contents )
|
||||
{
|
||||
contents.addAll( m_children.keySet() );
|
||||
}
|
||||
|
||||
|
||||
public void insertChild( FileInZip child )
|
||||
{
|
||||
String localPath = FileSystem.toLocal( child.getPath(), m_path );
|
||||
m_children.put( localPath, child );
|
||||
}
|
||||
|
||||
public FileInZip getFile( String path )
|
||||
public FileInZip getFile( String path )
|
||||
{
|
||||
// If we've reached the target, return this
|
||||
if( path.equals( m_path ) )
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, get the next component of the path
|
||||
String localPath = FileSystem.toLocal( path, m_path );
|
||||
int slash = localPath.indexOf( "/" );
|
||||
int slash = localPath.indexOf("/");
|
||||
if( slash >= 0 )
|
||||
{
|
||||
localPath = localPath.substring( 0, slash );
|
||||
@@ -86,17 +85,17 @@ public class JarMount implements IMount
|
||||
{
|
||||
return subFile.getFile( path );
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public FileInZip getParent( String path )
|
||||
{
|
||||
if( path.length() == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
FileInZip file = getFile( FileSystem.getDirectory( path ) );
|
||||
if( file.isDirectory() )
|
||||
{
|
||||
@@ -105,19 +104,18 @@ public class JarMount implements IMount
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ZipFile m_zipFile;
|
||||
private FileInZip m_root;
|
||||
private String m_rootPath;
|
||||
|
||||
@Deprecated
|
||||
public JarMount( File jarFile, String subPath ) throws IOException
|
||||
{
|
||||
if( !jarFile.exists() || jarFile.isDirectory() )
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
|
||||
// Open the zip file
|
||||
try
|
||||
{
|
||||
@@ -127,13 +125,13 @@ public class JarMount implements IMount
|
||||
{
|
||||
throw new IOException( "Error loading zip file" );
|
||||
}
|
||||
|
||||
|
||||
if( m_zipFile.getEntry( subPath ) == null )
|
||||
{
|
||||
m_zipFile.close();
|
||||
throw new IOException( "Zip does not contain path" );
|
||||
}
|
||||
|
||||
|
||||
// Read in all the entries
|
||||
Enumeration<? extends ZipEntry> zipEntries = m_zipFile.entries();
|
||||
while( zipEntries.hasMoreElements() )
|
||||
@@ -141,7 +139,7 @@ public class JarMount implements IMount
|
||||
ZipEntry entry = zipEntries.nextElement();
|
||||
String entryName = entry.getName();
|
||||
if( entryName.startsWith( subPath ) )
|
||||
{
|
||||
{
|
||||
entryName = FileSystem.toLocal( entryName, subPath );
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
return file != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
@@ -194,7 +192,7 @@ public class JarMount implements IMount
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
@@ -205,10 +203,10 @@ public class JarMount implements IMount
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "/" + path + ": Not a directory" );
|
||||
throw new IOException( "/" + path + ": Not a directory" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
@@ -217,7 +215,7 @@ public class JarMount implements IMount
|
||||
{
|
||||
return file.getSize();
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -245,6 +243,6 @@ public class JarMount implements IMount
|
||||
// treat errors as non-existance of file
|
||||
}
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import dan200.computercraft.api.filesystem.IMount;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
public class SubMount implements IMount
|
||||
@@ -53,19 +52,11 @@ public class SubMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
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 )
|
||||
{
|
||||
if( path.length() == 0 )
|
||||
|
@@ -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" );
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" );
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,13 +7,9 @@
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
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.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.compiler.CompileException;
|
||||
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.VarArgFunction;
|
||||
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.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -34,9 +29,6 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
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.ValueFactory.valueOf;
|
||||
@@ -44,19 +36,12 @@ import static org.squiddev.cobalt.ValueFactory.varargsOf;
|
||||
|
||||
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 LuaState m_state;
|
||||
private LuaTable m_globals;
|
||||
private LuaThread m_mainRoutine;
|
||||
private final LuaState m_state;
|
||||
private final LuaTable m_globals;
|
||||
|
||||
private LuaThread m_mainRoutine;
|
||||
private String m_eventFilter;
|
||||
private String m_softAbortMessage;
|
||||
private String m_hardAbortMessage;
|
||||
@@ -66,71 +51,60 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
m_computer = computer;
|
||||
|
||||
// Create an environment to run in
|
||||
LuaState state = this.m_state = LuaState.builder()
|
||||
.resourceManipulator( new VoidResourceManipulator() )
|
||||
.debug( new DebugHandler()
|
||||
final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
|
||||
{
|
||||
@Override
|
||||
public InputStream findResource( String filename )
|
||||
{
|
||||
private int count = 0;
|
||||
private boolean hasSoftAbort;
|
||||
return null;
|
||||
}
|
||||
} );
|
||||
state.debug = new DebugHandler( state )
|
||||
{
|
||||
private int count = 0;
|
||||
private boolean hasSoftAbort;
|
||||
|
||||
@Override
|
||||
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
|
||||
@Override
|
||||
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( count > 100000 )
|
||||
{
|
||||
if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE );
|
||||
this.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
handleSoftAbort();
|
||||
}
|
||||
|
||||
super.onInstruction( ds, di, pc, extras, top );
|
||||
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll() throws LuaError
|
||||
else
|
||||
{
|
||||
if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE );
|
||||
handleSoftAbort();
|
||||
}
|
||||
|
||||
private void handleSoftAbort() throws LuaError
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
super.onInstruction( ds, di, pc, extras, top );
|
||||
}
|
||||
|
||||
if( hasSoftAbort && m_hardAbortMessage == null )
|
||||
{
|
||||
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void poll() throws LuaError
|
||||
{
|
||||
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
|
||||
handleSoftAbort();
|
||||
}
|
||||
|
||||
hasSoftAbort = true;
|
||||
throw new LuaError( message );
|
||||
private void handleSoftAbort() throws LuaError {
|
||||
// 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 -> {
|
||||
Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 );
|
||||
coroutines.execute( () -> {
|
||||
try
|
||||
{
|
||||
command.run();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Tracking.addValue( m_computer, TrackingField.COROUTINES_DISPOSED, 1 );
|
||||
}
|
||||
} );
|
||||
} )
|
||||
.build();
|
||||
|
||||
if (hasSoftAbort && m_hardAbortMessage == null) {
|
||||
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
|
||||
return;
|
||||
}
|
||||
|
||||
hasSoftAbort = true;
|
||||
throw new LuaError(message);
|
||||
}
|
||||
};
|
||||
|
||||
m_globals = new LuaTable();
|
||||
state.setupThread( m_globals );
|
||||
@@ -145,10 +119,10 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
|
||||
|
||||
// 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
|
||||
// m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
m_globals.rawset( "dofile", Constants.NIL );
|
||||
m_globals.rawset( "loadfile", Constants.NIL );
|
||||
m_globals.rawset( "print", Constants.NIL );
|
||||
@@ -186,7 +160,10 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
public void loadBios( InputStream bios )
|
||||
{
|
||||
// Begin executing a file (ie, the bios)
|
||||
if( m_mainRoutine != null ) return;
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -195,19 +172,30 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
}
|
||||
catch( CompileException e )
|
||||
{
|
||||
unload();
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
ComputerCraft.log.warn( "Could not load bios.lua ", e );
|
||||
unload();
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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" ) )
|
||||
{
|
||||
@@ -222,26 +210,34 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
|
||||
}
|
||||
|
||||
Varargs results = m_mainRoutine.resume( resumeArgs );
|
||||
LuaValue filter = LuaThread.run( m_mainRoutine, resumeArgs ).first();
|
||||
if( m_hardAbortMessage != null )
|
||||
{
|
||||
throw new LuaError( m_hardAbortMessage );
|
||||
}
|
||||
else if( !results.first().checkBoolean() )
|
||||
{
|
||||
throw new LuaError( results.arg( 2 ).checkString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
LuaValue filter = results.arg( 2 );
|
||||
m_eventFilter = filter.isString() ? filter.toString() : null;
|
||||
if( filter.isString() )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
unload();
|
||||
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Main thread crashed", e );
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -278,199 +274,30 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
@Override
|
||||
public boolean isFinished()
|
||||
{
|
||||
return m_mainRoutine == null;
|
||||
return (m_mainRoutine == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload()
|
||||
{
|
||||
if( m_state == null ) return;
|
||||
|
||||
m_state.abandon();
|
||||
m_mainRoutine = null;
|
||||
m_state = null;
|
||||
m_globals = null;
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
LuaThread mainThread = m_mainRoutine;
|
||||
mainThread.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private LuaTable wrapLuaObject( ILuaObject object )
|
||||
{
|
||||
LuaTable table = new LuaTable();
|
||||
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 ILuaObject apiObject = object;
|
||||
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 );
|
||||
}
|
||||
} );
|
||||
final String methodName = methods[method];
|
||||
table.rawset( methodName, new CobaltWrapperFunction( this, m_computer, object, method, methodName ) );
|
||||
}
|
||||
}
|
||||
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 )
|
||||
{
|
||||
@@ -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();
|
||||
Object[] objects = new Object[ count - startIdx + 1 ];
|
||||
@@ -685,7 +512,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
private byte[] bytes;
|
||||
private int offset, remaining = 0;
|
||||
|
||||
StringInputStream( LuaState state, LuaValue func )
|
||||
public StringInputStream( LuaState state, LuaValue func )
|
||||
{
|
||||
this.state = state;
|
||||
this.func = func;
|
||||
@@ -697,13 +524,23 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
if( remaining <= 0 )
|
||||
{
|
||||
LuaValue s;
|
||||
state.getCurrentThread().disableYield();
|
||||
try
|
||||
{
|
||||
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 );
|
||||
}
|
||||
finally
|
||||
{
|
||||
state.getCurrentThread().enableYield();
|
||||
}
|
||||
|
||||
if( s.isNil() )
|
||||
{
|
||||
@@ -729,4 +566,5 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
return bytes[offset++];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
38
src/main/java/dan200/computercraft/core/lua/Semaphore.java
Normal file
38
src/main/java/dan200/computercraft/core/lua/Semaphore.java
Normal 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;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
||||
import gnu.trove.map.hash.TObjectLongHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.ref.WeakReference;
|
||||
@@ -18,13 +18,13 @@ public class ComputerTracker
|
||||
private long serverCount;
|
||||
private long serverTime;
|
||||
|
||||
private final Object2LongOpenHashMap<TrackingField> fields;
|
||||
private final TObjectLongHashMap<TrackingField> fields;
|
||||
|
||||
public ComputerTracker( Computer computer )
|
||||
{
|
||||
this.computer = new WeakReference<>( computer );
|
||||
this.computerId = computer.getID();
|
||||
this.fields = new Object2LongOpenHashMap<>();
|
||||
this.fields = new TObjectLongHashMap<>();
|
||||
}
|
||||
|
||||
ComputerTracker( ComputerTracker timings )
|
||||
@@ -39,7 +39,7 @@ public class ComputerTracker
|
||||
this.serverCount = timings.serverCount;
|
||||
this.serverTime = timings.serverTime;
|
||||
|
||||
this.fields = new Object2LongOpenHashMap<>( timings.fields );
|
||||
this.fields = new TObjectLongHashMap<>( timings.fields );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -90,7 +90,7 @@ public class ComputerTracker
|
||||
{
|
||||
synchronized( fields )
|
||||
{
|
||||
fields.addTo( field, change );
|
||||
fields.adjustOrPutValue( field, change, change );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public class ComputerTracker
|
||||
|
||||
synchronized( fields )
|
||||
{
|
||||
return fields.getLong( field );
|
||||
return fields.get( field );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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_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 displayName;
|
||||
private final LongFunction<String> format;
|
||||
|
@@ -8,9 +8,7 @@ package dan200.computercraft.shared.computer.apis;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -24,6 +22,7 @@ import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -82,7 +81,7 @@ public class CommandAPI implements ILuaAPI
|
||||
sender.clearOutput();
|
||||
|
||||
int result = commandManager.executeCommand( sender, command );
|
||||
return new Object[]{ (result > 0), sender.copyOutput() };
|
||||
return new Object[] { (result > 0), sender.copyOutput() };
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
@@ -90,7 +89,7 @@ public class CommandAPI implements ILuaAPI
|
||||
{
|
||||
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
|
||||
@@ -131,7 +130,8 @@ public class CommandAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@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 )
|
||||
{
|
||||
@@ -139,19 +139,19 @@ public class CommandAPI implements ILuaAPI
|
||||
{
|
||||
// exec
|
||||
final String command = getString( arguments, 0 );
|
||||
return context.executeMainThreadTask( () -> doCommand( command ) );
|
||||
return MethodResult.onMainThread( () -> MethodResult.of( doCommand( command ) ) );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// execAsync
|
||||
final String command = getString( arguments, 0 );
|
||||
long taskID = context.issueMainThreadTask( () -> doCommand( command ) );
|
||||
return new Object[] { taskID };
|
||||
return MethodResult.of( taskID );
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// list
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
int i = 1;
|
||||
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:
|
||||
@@ -190,7 +190,7 @@ public class CommandAPI implements ILuaAPI
|
||||
// getBlockPosition
|
||||
// This is probably safe to do on the Lua thread. Probably.
|
||||
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:
|
||||
{
|
||||
@@ -201,7 +201,7 @@ public class CommandAPI implements ILuaAPI
|
||||
final int maxx = getInt( arguments, 3 );
|
||||
final int maxy = getInt( arguments, 4 );
|
||||
final int maxz = getInt( arguments, 5 );
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
// Get the details of the block
|
||||
World world = m_computer.getWorld();
|
||||
@@ -236,7 +236,7 @@ public class CommandAPI implements ILuaAPI
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Object[]{ results };
|
||||
return MethodResult.of( results );
|
||||
} );
|
||||
}
|
||||
case 5:
|
||||
@@ -245,14 +245,14 @@ public class CommandAPI implements ILuaAPI
|
||||
final int x = getInt( arguments, 0 );
|
||||
final int y = getInt( arguments, 1 );
|
||||
final int z = getInt( arguments, 2 );
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
// Get the details of the block
|
||||
World world = m_computer.getWorld();
|
||||
BlockPos position = new BlockPos( x, y, z );
|
||||
if( WorldUtil.isBlockInWorld( world, position ) )
|
||||
{
|
||||
return new Object[]{ getBlockInfo( world, position ) };
|
||||
return MethodResult.of( getBlockInfo( world, position ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,8 +262,16 @@ public class CommandAPI implements ILuaAPI
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,15 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
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.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ComputerPeripheral
|
||||
implements IPeripheral
|
||||
@@ -23,7 +27,7 @@ public class ComputerPeripheral
|
||||
m_type = type;
|
||||
m_computer = computer;
|
||||
}
|
||||
|
||||
|
||||
// IPeripheral implementation
|
||||
|
||||
@Nonnull
|
||||
@@ -47,8 +51,9 @@ public class ComputerPeripheral
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -56,42 +61,48 @@ public class ComputerPeripheral
|
||||
{
|
||||
// turnOn
|
||||
m_computer.turnOn();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// shutdown
|
||||
m_computer.shutdown();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// reboot
|
||||
m_computer.reboot();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// getID
|
||||
return new Object[] {
|
||||
m_computer.assignID()
|
||||
};
|
||||
return MethodResult.of( m_computer.assignID() );
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// isOn
|
||||
return new Object[] { m_computer.isOn() };
|
||||
return MethodResult.of( m_computer.isOn() );
|
||||
}
|
||||
case 5:
|
||||
// getLabel
|
||||
return new Object[] { m_computer.getLabel() };
|
||||
return MethodResult.of( m_computer.getLabel() );
|
||||
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
|
||||
public boolean equals( IPeripheral other )
|
||||
{
|
||||
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -69,6 +69,7 @@ final class BundledCapabilityProvider implements ICapabilityProvider
|
||||
emitter = emitters[index] = () -> toBytes( tile.getBundledRedstoneOutput( side ) );
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
return CAPABILITY_EMITTER.cast( emitter );
|
||||
}
|
||||
|
@@ -6,14 +6,17 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.commandblock;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
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.IPeripheral;
|
||||
import net.minecraft.tileentity.TileEntityCommandBlock;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
|
||||
|
||||
@@ -46,17 +49,18 @@ public class CommandBlockPeripheral implements IPeripheral
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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:
|
||||
{
|
||||
// getCommand
|
||||
return context.executeMainThreadTask( () -> new Object[] {
|
||||
m_commandBlock.getCommandBlockLogic().getCommand()
|
||||
} );
|
||||
return MethodResult.onMainThread( () ->
|
||||
MethodResult.of( m_commandBlock.getCommandBlockLogic().getCommand() )
|
||||
);
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -69,27 +73,35 @@ public class CommandBlockPeripheral implements IPeripheral
|
||||
m_commandBlock.getWorld().markBlockRangeForRenderUpdate( pos, pos );
|
||||
return null;
|
||||
} );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// runCommand
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
m_commandBlock.getCommandBlockLogic().trigger( m_commandBlock.getWorld() );
|
||||
int result = m_commandBlock.getCommandBlockLogic().getSuccessCount();
|
||||
if( result > 0 )
|
||||
{
|
||||
return new Object[] { true };
|
||||
return MethodResult.of( true );
|
||||
}
|
||||
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
|
||||
|
@@ -6,8 +6,10 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.diskdrive;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -17,6 +19,7 @@ import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
|
||||
|
||||
@@ -55,17 +58,18 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// isDiskPresent
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
!m_diskDrive.getDiskStack().isEmpty()
|
||||
};
|
||||
);
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -73,9 +77,9 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
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:
|
||||
{
|
||||
@@ -96,21 +100,21 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
throw new LuaException( "Disk label cannot be changed" );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// hasData
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
m_diskDrive.getDiskMountPath( computer ) != null
|
||||
};
|
||||
);
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// getMountPath
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
m_diskDrive.getDiskMountPath( computer )
|
||||
};
|
||||
);
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
@@ -118,9 +122,9 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
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:
|
||||
{
|
||||
@@ -128,27 +132,27 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
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:
|
||||
{
|
||||
// playAudio
|
||||
m_diskDrive.playDiskAudio();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
// stopAudio
|
||||
m_diskDrive.stopDiskAudio();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 9:
|
||||
{
|
||||
// eject
|
||||
m_diskDrive.ejectDisk();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 10:
|
||||
{
|
||||
@@ -159,18 +163,27 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
Item item = disk.getItem();
|
||||
if( item instanceof ItemDiskLegacy )
|
||||
{
|
||||
return new Object[] { ((ItemDiskLegacy)item).getDiskID( disk ) };
|
||||
return MethodResult.of( ((ItemDiskLegacy)item).getDiskID( disk ) );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
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
|
||||
public void attach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
|
@@ -6,51 +6,64 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.network.IPacketReceiver;
|
||||
import dan200.computercraft.api.network.IPacketSender;
|
||||
import dan200.computercraft.api.network.Packet;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
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.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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 final Set<IComputerAccess> m_computers = new HashSet<>( 1 );
|
||||
private final ModemState m_state;
|
||||
private IComputerAccess m_computer;
|
||||
private final TIntSet m_channels;
|
||||
|
||||
protected ModemPeripheral( ModemState state )
|
||||
{
|
||||
this.m_state = state;
|
||||
}
|
||||
private boolean m_open;
|
||||
private boolean m_changed;
|
||||
|
||||
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 )
|
||||
{
|
||||
if( m_network == network ) return;
|
||||
if( m_network != network )
|
||||
{
|
||||
// Leave old network
|
||||
if( m_network != null )
|
||||
{
|
||||
m_network.removeReceiver( this );
|
||||
}
|
||||
|
||||
// Leave old network
|
||||
if( m_network != null ) m_network.removeReceiver( this );
|
||||
// Set new network
|
||||
m_network = network;
|
||||
|
||||
// Set new network
|
||||
m_network = network;
|
||||
|
||||
// Join new network
|
||||
if( m_network != null ) m_network.addReceiver( this );
|
||||
// Join new network
|
||||
if( m_network != null )
|
||||
{
|
||||
m_network.addReceiver( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void switchNetwork()
|
||||
@@ -58,22 +71,39 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
setNetwork( getNetwork() );
|
||||
}
|
||||
|
||||
public void destroy()
|
||||
public synchronized void destroy()
|
||||
{
|
||||
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
|
||||
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[]{
|
||||
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
|
||||
m_computer.queueEvent( "modem_message", new Object[] {
|
||||
m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -82,21 +112,21 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
@Override
|
||||
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[]{
|
||||
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
|
||||
m_computer.queueEvent( "modem_message", new Object[] {
|
||||
m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IPacketNetwork getNetwork();
|
||||
|
||||
|
||||
// IPeripheral implementation
|
||||
|
||||
@Nonnull
|
||||
@@ -105,12 +135,12 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
{
|
||||
return "modem";
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[]{
|
||||
return new String[] {
|
||||
"open",
|
||||
"isOpen",
|
||||
"close",
|
||||
@@ -119,7 +149,7 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
"isWireless",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static int parseChannel( Object[] arguments, int index ) throws LuaException
|
||||
{
|
||||
int channel = getInt( arguments, index );
|
||||
@@ -129,9 +159,10 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -139,39 +170,81 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
{
|
||||
// open
|
||||
int channel = parseChannel( arguments, 0 );
|
||||
m_state.open( channel );
|
||||
return null;
|
||||
synchronized( this )
|
||||
{
|
||||
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:
|
||||
{
|
||||
// isOpen
|
||||
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:
|
||||
{
|
||||
// close
|
||||
int channel = parseChannel( arguments, 0 );
|
||||
m_state.close( channel );
|
||||
return null;
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_channels.remove( channel ) )
|
||||
{
|
||||
if( m_channels.size() == 0 )
|
||||
{
|
||||
m_open = false;
|
||||
m_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// closeAll
|
||||
m_state.closeAll();
|
||||
return null;
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_channels.size() > 0 )
|
||||
{
|
||||
m_channels.clear();
|
||||
|
||||
if( m_open )
|
||||
{
|
||||
m_open = false;
|
||||
m_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// transmit
|
||||
int channel = parseChannel( arguments, 0 );
|
||||
int replyChannel = parseChannel( arguments, 1 );
|
||||
Object payload = arguments.length > 2 ? arguments[2] : null;
|
||||
Object payload = (arguments.length >= 3) ? arguments[2] : null;
|
||||
synchronized( this )
|
||||
{
|
||||
World world = getWorld();
|
||||
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 );
|
||||
if( isInterdimensional() )
|
||||
@@ -184,60 +257,78 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// isWireless
|
||||
IPacketNetwork network = m_network;
|
||||
return new Object[]{ network != null && network.isWireless() };
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_network != null )
|
||||
{
|
||||
return MethodResult.of( m_network.isWireless() );
|
||||
}
|
||||
}
|
||||
return MethodResult.of(false);
|
||||
}
|
||||
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
|
||||
public synchronized void attach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
synchronized( m_computers )
|
||||
{
|
||||
m_computers.add( computer );
|
||||
}
|
||||
|
||||
m_computer = computer;
|
||||
setNetwork( getNetwork() );
|
||||
m_open = !m_channels.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void detach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
boolean empty;
|
||||
synchronized( m_computers )
|
||||
if( m_network != null )
|
||||
{
|
||||
m_computers.remove( computer );
|
||||
empty = m_computers.isEmpty();
|
||||
m_network.removeReceiver( this );
|
||||
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
|
||||
@Override
|
||||
public String getSenderID()
|
||||
{
|
||||
synchronized( m_computers )
|
||||
if( m_computer == null )
|
||||
{
|
||||
if( m_computers.size() != 1 )
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
else
|
||||
{
|
||||
IComputerAccess computer = m_computers.iterator().next();
|
||||
return computer.getID() + "_" + computer.getAttachmentName();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_computer.getID() + "_" + m_computer.getAttachmentName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ public class TileAdvancedModem extends TileModemBase
|
||||
|
||||
public Peripheral( TileModemBase entity )
|
||||
{
|
||||
super( new ModemState(), true );
|
||||
super( true );
|
||||
m_entity = entity;
|
||||
}
|
||||
|
||||
|
@@ -97,7 +97,7 @@ public class TileCable extends TileModemBase
|
||||
|
||||
private boolean m_hasDirection = false;
|
||||
private boolean m_connectionsFormed = false;
|
||||
|
||||
|
||||
private WiredModemElement m_cable;
|
||||
private IWiredNode m_node;
|
||||
|
||||
@@ -106,7 +106,7 @@ public class TileCable extends TileModemBase
|
||||
{
|
||||
m_cable = new CableElement( this );
|
||||
m_node = m_cable.getNode();
|
||||
return new WiredModemPeripheral( new ModemState(), m_cable )
|
||||
return new WiredModemPeripheral( m_cable )
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
@@ -453,8 +453,14 @@ public class TileCable extends TileModemBase
|
||||
protected void updateAnim()
|
||||
{
|
||||
int anim = 0;
|
||||
if( m_modem.getModemState().isOpen() ) anim |= 1;
|
||||
if( m_peripheralAccessAllowed ) anim |= 2;
|
||||
if( m_modem.isActive() )
|
||||
{
|
||||
anim += 1;
|
||||
}
|
||||
if( m_peripheralAccessAllowed )
|
||||
{
|
||||
anim += 2;
|
||||
}
|
||||
setAnim( anim );
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,10 @@ public abstract class TileModemBase extends TilePeripheralBase
|
||||
public void onNeighbourChange()
|
||||
{
|
||||
EnumFacing dir = getDirection();
|
||||
if( !getWorld().isSideSolid( getPos().offset( dir ), dir.getOpposite() ) )
|
||||
if( !getWorld().isSideSolid(
|
||||
getPos().offset( dir ),
|
||||
dir.getOpposite()
|
||||
) )
|
||||
{
|
||||
// Drop everything and remove block
|
||||
((BlockGeneric)getBlockType()).dropAllItems( getWorld(), getPos(), false );
|
||||
@@ -76,15 +79,22 @@ public abstract class TileModemBase extends TilePeripheralBase
|
||||
public void update()
|
||||
{
|
||||
super.update();
|
||||
if( !getWorld().isRemote && m_modem.getModemState().pollChanged() )
|
||||
if( !getWorld().isRemote && m_modem.pollChanged() )
|
||||
{
|
||||
updateAnim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void updateAnim()
|
||||
{
|
||||
setAnim( m_modem.getModemState().isOpen() ? 1 : 0 );
|
||||
if( m_modem.isActive() )
|
||||
{
|
||||
setAnim(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAnim(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,6 +109,15 @@ public abstract class TileModemBase extends TilePeripheralBase
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -83,7 +83,6 @@ public class TileWiredModemFull extends TilePeripheralBase
|
||||
private boolean m_destroyed = false;
|
||||
private boolean m_connectionsFormed = false;
|
||||
|
||||
private final ModemState m_modemState = new ModemState();
|
||||
private final WiredModemElement m_element = new FullElement( this );
|
||||
private final IWiredNode m_node = m_element.getNode();
|
||||
|
||||
@@ -237,8 +236,19 @@ public class TileWiredModemFull extends TilePeripheralBase
|
||||
protected void updateAnim()
|
||||
{
|
||||
int anim = 0;
|
||||
if( m_modemState.isOpen() ) anim |= 1;
|
||||
if( m_peripheralAccessAllowed ) anim |= 2;
|
||||
for( WiredModemPeripheral modem : m_modems )
|
||||
{
|
||||
if( modem != null && modem.isActive() )
|
||||
{
|
||||
anim += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( m_peripheralAccessAllowed )
|
||||
{
|
||||
anim += 2;
|
||||
}
|
||||
setAnim( anim );
|
||||
}
|
||||
|
||||
@@ -254,7 +264,12 @@ public class TileWiredModemFull extends TilePeripheralBase
|
||||
{
|
||||
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 )
|
||||
{
|
||||
@@ -387,7 +402,7 @@ public class TileWiredModemFull extends TilePeripheralBase
|
||||
if( peripheral == null )
|
||||
{
|
||||
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
|
||||
@Override
|
||||
|
@@ -29,7 +29,7 @@ public class TileWirelessModem extends TileModemBase
|
||||
|
||||
public Peripheral( TileModemBase entity )
|
||||
{
|
||||
super( new ModemState(), false );
|
||||
super( false );
|
||||
m_entity = entity;
|
||||
}
|
||||
|
||||
|
@@ -3,8 +3,9 @@ package dan200.computercraft.shared.peripheral.modem;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
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.MethodResult;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.network.wired.IWiredSender;
|
||||
@@ -18,8 +19,6 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -82,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
return newMethods;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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();
|
||||
switch( method - methods.length )
|
||||
@@ -91,60 +90,62 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
case 0:
|
||||
{
|
||||
// getNamesRemote
|
||||
Map<String, RemotePeripheralWrapper> wrappers = getWrappers( computer );
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
if( wrappers != null )
|
||||
synchronized( peripheralWrappers )
|
||||
{
|
||||
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:
|
||||
{
|
||||
// isPresentRemote
|
||||
String name = getString( arguments, 0 );
|
||||
return new Object[]{ getWrapper( computer, name ) != null };
|
||||
String type = getTypeRemote( getString( arguments, 0 ) );
|
||||
return MethodResult.of( type != null );
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// getTypeRemote
|
||||
String name = getString( arguments, 0 );
|
||||
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
|
||||
return wrapper != null ? new Object[]{ wrapper.getType() } : null;
|
||||
String type = getTypeRemote( getString( arguments, 0 ) );
|
||||
if( type != null )
|
||||
{
|
||||
return MethodResult.of( type );
|
||||
}
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// getMethodsRemote
|
||||
String name = getString( arguments, 0 );
|
||||
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
|
||||
if( wrapper == null ) return null;
|
||||
|
||||
String[] methodNames = wrapper.getMethodNames();
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
for( int i = 0; i < methodNames.length; ++i )
|
||||
String[] methodNames = getMethodNamesRemote( getString( arguments, 0 ) );
|
||||
if( methodNames != null )
|
||||
{
|
||||
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:
|
||||
{
|
||||
// callRemote
|
||||
String remoteName = getString( arguments, 0 );
|
||||
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];
|
||||
System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 );
|
||||
return wrapper.callMethod( context, methodName, methodArgs );
|
||||
return callMethodRemote( remoteName, context, methodName, methodArgs );
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// getNameLocal
|
||||
String local = getLocalPeripheral().getConnectedName();
|
||||
return local == null ? null : new Object[]{ local };
|
||||
return local == null ? MethodResult.empty() : MethodResult.of( local );
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -158,37 +159,29 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
public void attach( @Nonnull IComputerAccess 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() )
|
||||
{
|
||||
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
|
||||
public void detach( @Nonnull IComputerAccess computer )
|
||||
public synchronized void detach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
Map<String, RemotePeripheralWrapper> wrappers;
|
||||
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 );
|
||||
}
|
||||
|
||||
@@ -213,12 +206,11 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
|
||||
public void attachPeripheral( String name, IPeripheral peripheral )
|
||||
{
|
||||
if( getComputer() == null ) return;
|
||||
|
||||
synchronized( peripheralWrappers )
|
||||
{
|
||||
for( Map.Entry<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> entry : peripheralWrappers.entrySet() )
|
||||
{
|
||||
attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral );
|
||||
}
|
||||
attachPeripheralImpl( name, peripheral );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,35 +218,63 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
{
|
||||
synchronized( peripheralWrappers )
|
||||
{
|
||||
for(ConcurrentMap<String, RemotePeripheralWrapper> wrappers : peripheralWrappers.values()) {
|
||||
RemotePeripheralWrapper wrapper = wrappers.remove( name );
|
||||
if( wrapper != null ) wrapper.detach();
|
||||
RemotePeripheralWrapper wrapper = peripheralWrappers.get( name );
|
||||
if( wrapper != null )
|
||||
{
|
||||
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 );
|
||||
peripherals.put( periphName, wrapper );
|
||||
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
|
||||
peripheralWrappers.put( periphName, wrapper );
|
||||
wrapper.attach();
|
||||
}
|
||||
}
|
||||
|
||||
private ConcurrentMap<String, RemotePeripheralWrapper> getWrappers( IComputerAccess computer ) {
|
||||
|
||||
private String getTypeRemote( String remoteName )
|
||||
{
|
||||
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 );
|
||||
return wrappers == null ? null : wrappers.get( remoteName );
|
||||
synchronized( peripheralWrappers )
|
||||
{
|
||||
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
|
||||
@@ -312,7 +332,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
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 ) )
|
||||
{
|
||||
|
@@ -15,16 +15,9 @@ public abstract class WirelessModemPeripheral extends ModemPeripheral
|
||||
{
|
||||
private boolean m_advanced;
|
||||
|
||||
public WirelessModemPeripheral( ModemState state, boolean advanced )
|
||||
{
|
||||
super( state );
|
||||
m_advanced = advanced;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public WirelessModemPeripheral( boolean advanced )
|
||||
{
|
||||
this( new ModemState(), advanced );
|
||||
m_advanced = advanced;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -13,9 +13,8 @@ import dan200.computercraft.api.network.IPacketSender;
|
||||
import dan200.computercraft.api.network.Packet;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class WirelessNetwork implements IPacketNetwork
|
||||
{
|
||||
@@ -35,34 +34,45 @@ public class WirelessNetwork implements IPacketNetwork
|
||||
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
|
||||
public void addReceiver( @Nonnull IPacketReceiver receiver )
|
||||
public synchronized void addReceiver( @Nonnull IPacketReceiver receiver )
|
||||
{
|
||||
Preconditions.checkNotNull( receiver, "device cannot be null" );
|
||||
m_receivers.add( receiver );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReceiver( @Nonnull IPacketReceiver receiver )
|
||||
public synchronized void removeReceiver( @Nonnull IPacketReceiver receiver )
|
||||
{
|
||||
Preconditions.checkNotNull( receiver, "device cannot be null" );
|
||||
m_receivers.remove( receiver );
|
||||
}
|
||||
|
||||
@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" );
|
||||
for( IPacketReceiver device : m_receivers ) tryTransmit( device, packet, range, false );
|
||||
for( IPacketReceiver device : m_receivers )
|
||||
{
|
||||
tryTransmit( device, packet, range, false );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transmitInterdimensional( @Nonnull Packet packet )
|
||||
public synchronized void transmitInterdimensional( @Nonnull Packet packet )
|
||||
{
|
||||
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 )
|
||||
|
@@ -6,8 +6,10 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.monitor;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
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.IPeripheral;
|
||||
import dan200.computercraft.core.apis.TermAPI;
|
||||
@@ -16,6 +18,7 @@ import dan200.computercraft.shared.util.Palette;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
|
||||
@@ -66,13 +69,13 @@ public class MonitorPeripheral implements IPeripheral
|
||||
"setPaletteColor",
|
||||
"getPaletteColour",
|
||||
"getPaletteColor",
|
||||
"getTextScale",
|
||||
"getCursorBlink",
|
||||
"getTextScale"
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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();
|
||||
if( monitor == null ) throw new LuaException( "Monitor has been detatched" );
|
||||
@@ -86,21 +89,24 @@ public class MonitorPeripheral implements IPeripheral
|
||||
{
|
||||
// write
|
||||
String text;
|
||||
if( args.length > 0 && args[0] != null ) {
|
||||
text = args[0].toString();
|
||||
} else {
|
||||
if( args.length > 0 && args[ 0 ] != null )
|
||||
{
|
||||
text = args[ 0 ].toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
text = "";
|
||||
}
|
||||
terminal.write( text );
|
||||
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// scroll
|
||||
int value = getInt( args, 0 );
|
||||
terminal.scroll( value );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
@@ -108,42 +114,42 @@ public class MonitorPeripheral implements IPeripheral
|
||||
int x = getInt( args, 0 ) - 1;
|
||||
int y = getInt( args, 1 ) - 1;
|
||||
terminal.setCursorPos( x, y );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// setCursorBlink
|
||||
boolean blink = getBoolean( args, 0 );
|
||||
terminal.setCursorBlink( blink );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// getCursorPos
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
terminal.getCursorX() + 1,
|
||||
terminal.getCursorY() + 1
|
||||
};
|
||||
);
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// getSize
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
terminal.getWidth(),
|
||||
terminal.getHeight()
|
||||
};
|
||||
);
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// clear
|
||||
terminal.clear();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
// clearLine
|
||||
terminal.clearLine();
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
@@ -154,7 +160,7 @@ public class MonitorPeripheral implements IPeripheral
|
||||
throw new LuaException( "Expected number in range 0.5-5" );
|
||||
}
|
||||
monitor.setTextScale( scale );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 9:
|
||||
case 10:
|
||||
@@ -162,7 +168,7 @@ public class MonitorPeripheral implements IPeripheral
|
||||
// setTextColour/setTextColor
|
||||
int colour = TermAPI.parseColour( args );
|
||||
terminal.setTextColour( colour );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 11:
|
||||
case 12:
|
||||
@@ -170,15 +176,15 @@ public class MonitorPeripheral implements IPeripheral
|
||||
// setBackgroundColour/setBackgroundColor
|
||||
int colour = TermAPI.parseColour( args );
|
||||
terminal.setBackgroundColour( colour );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 13:
|
||||
case 14:
|
||||
{
|
||||
// isColour/isColor
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
monitor.isColour()
|
||||
};
|
||||
);
|
||||
}
|
||||
case 15:
|
||||
case 16:
|
||||
@@ -205,7 +211,7 @@ public class MonitorPeripheral implements IPeripheral
|
||||
|
||||
terminal.blit( text, textColour, backgroundColour );
|
||||
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 20:
|
||||
case 21:
|
||||
@@ -225,7 +231,7 @@ public class MonitorPeripheral implements IPeripheral
|
||||
double b = getReal( args, 3 );
|
||||
TermAPI.setColour( terminal, colour, r, g, b );
|
||||
}
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 22:
|
||||
case 23:
|
||||
@@ -237,21 +243,25 @@ public class MonitorPeripheral implements IPeripheral
|
||||
|
||||
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:
|
||||
{
|
||||
// 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
|
||||
@@ -271,7 +281,7 @@ public class MonitorPeripheral implements IPeripheral
|
||||
{
|
||||
if( other != null && other instanceof MonitorPeripheral )
|
||||
{
|
||||
MonitorPeripheral otherMonitor = (MonitorPeripheral)other;
|
||||
MonitorPeripheral otherMonitor = (MonitorPeripheral) other;
|
||||
if( otherMonitor.m_monitor == this.m_monitor )
|
||||
{
|
||||
return true;
|
||||
|
@@ -6,13 +6,16 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.printer;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
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.IPeripheral;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
|
||||
@@ -50,8 +53,9 @@ public class PrinterPeripheral implements IPeripheral
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
@@ -68,7 +72,7 @@ public class PrinterPeripheral implements IPeripheral
|
||||
Terminal page = getCurrentPage();
|
||||
page.write( text );
|
||||
page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
@@ -77,7 +81,7 @@ public class PrinterPeripheral implements IPeripheral
|
||||
int y = getInt( args, 1 ) - 1;
|
||||
Terminal page = getCurrentPage();
|
||||
page.setCursorPos( x, y );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
@@ -85,7 +89,7 @@ public class PrinterPeripheral implements IPeripheral
|
||||
Terminal page = getCurrentPage();
|
||||
int x = page.getCursorX();
|
||||
int y = page.getCursorY();
|
||||
return new Object[] { x + 1, y + 1 };
|
||||
return MethodResult.of( x + 1, y + 1 );
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
@@ -93,23 +97,23 @@ public class PrinterPeripheral implements IPeripheral
|
||||
Terminal page = getCurrentPage();
|
||||
int width = page.getWidth();
|
||||
int height = page.getHeight();
|
||||
return new Object[] { width, height };
|
||||
return MethodResult.of( width, height );
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// newPage
|
||||
return new Object[] { m_printer.startNewPage() };
|
||||
return MethodResult.of( m_printer.startNewPage() );
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// endPage
|
||||
getCurrentPage();
|
||||
return new Object[] { m_printer.endCurrentPage() };
|
||||
return MethodResult.of( m_printer.endCurrentPage() );
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// getInkLevel
|
||||
return new Object[] { m_printer.getInkLevel() };
|
||||
return MethodResult.of( m_printer.getInkLevel() );
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
@@ -117,20 +121,28 @@ public class PrinterPeripheral implements IPeripheral
|
||||
String title = optString( args, 0, "" );
|
||||
getCurrentPage();
|
||||
m_printer.setPageTitle( title );
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
// getPaperLevel
|
||||
return new Object[] { m_printer.getPaperLevel() };
|
||||
return MethodResult.of( m_printer.getPaperLevel() );
|
||||
}
|
||||
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
|
||||
public boolean equals( IPeripheral other )
|
||||
{
|
||||
|
@@ -7,12 +7,9 @@
|
||||
package dan200.computercraft.shared.peripheral.speaker;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
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.SoundCategory;
|
||||
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.optReal;
|
||||
|
||||
public class SpeakerPeripheral implements IPeripheral
|
||||
{
|
||||
private final TileSpeaker m_speaker;
|
||||
public class SpeakerPeripheral implements IPeripheral {
|
||||
private TileSpeaker m_speaker;
|
||||
private long m_clock;
|
||||
private long m_lastPlayTime;
|
||||
private final AtomicInteger m_notesThisTick;
|
||||
|
||||
public SpeakerPeripheral()
|
||||
{
|
||||
this( null );
|
||||
}
|
||||
|
||||
SpeakerPeripheral( TileSpeaker speaker )
|
||||
{
|
||||
m_clock = 0;
|
||||
m_lastPlayTime = 0;
|
||||
m_notesThisTick = new AtomicInteger( );
|
||||
}
|
||||
|
||||
SpeakerPeripheral(TileSpeaker speaker)
|
||||
{
|
||||
this();
|
||||
m_speaker = speaker;
|
||||
}
|
||||
|
||||
@@ -72,9 +68,15 @@ public class SpeakerPeripheral implements IPeripheral
|
||||
@Override
|
||||
public boolean equals( IPeripheral other )
|
||||
{
|
||||
if( other == this ) return true;
|
||||
if( !(other instanceof SpeakerPeripheral) ) return false;
|
||||
return m_speaker == ((SpeakerPeripheral) other).m_speaker;
|
||||
if( other != null && other instanceof SpeakerPeripheral )
|
||||
{
|
||||
SpeakerPeripheral otherSpeaker = (SpeakerPeripheral) other;
|
||||
return otherSpeaker.m_speaker == m_speaker;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -89,90 +91,116 @@ public class SpeakerPeripheral implements IPeripheral
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"playSound", // Plays sound at resourceLocator
|
||||
"playNote" // Plays note
|
||||
"playSound", // Plays sound at resourceLocator
|
||||
"playNote" // Plays note
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
// playSound
|
||||
case 0:
|
||||
{
|
||||
String name = getString( args, 0 );
|
||||
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 ) };
|
||||
return MethodResult.of( playSound( args, context, false ) );
|
||||
}
|
||||
|
||||
// playNote
|
||||
case 1:
|
||||
{
|
||||
return playNote( args, context );
|
||||
return MethodResult.of( playNote( args, context ) );
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new LuaException( "Method index out of range!" );
|
||||
throw new LuaException("Method index out of range!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException
|
||||
@Nullable
|
||||
@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 pitch = (float) optReal( arguments, 2, 1.0 );
|
||||
|
||||
String noteName = "block.note." + name;
|
||||
|
||||
// Check if the note exists
|
||||
if( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( noteName ) ) )
|
||||
// Check if sound exists
|
||||
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
|
||||
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();
|
||||
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) )
|
||||
if( success )
|
||||
{
|
||||
// Rate limiting occurs when we've already played a sound within the last tick, or we've
|
||||
// played more notes than allowable within the current tick.
|
||||
return false;
|
||||
m_notesThisTick.incrementAndGet();
|
||||
}
|
||||
|
||||
World world = getWorld();
|
||||
BlockPos pos = getPos();
|
||||
return success;
|
||||
}
|
||||
|
||||
context.issueMainThreadTask( () -> {
|
||||
MinecraftServer server = world.getMinecraftServer();
|
||||
if( server == null ) return null;
|
||||
private synchronized boolean playSound( Object[] arguments, ICallContext context, boolean isNote ) throws LuaException
|
||||
{
|
||||
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;
|
||||
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;
|
||||
} );
|
||||
ResourceLocation resourceName = new ResourceLocation( name );
|
||||
|
||||
m_lastPlayTime = m_clock;
|
||||
return true;
|
||||
if( m_clock - m_lastPlayTime >= TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS || (isNote && m_clock - m_lastPlayTime == 0 && m_notesThisTick.get() < ComputerCraft.maxNotesPerTick) )
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,9 +7,7 @@
|
||||
package dan200.computercraft.shared.pocket.apis;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
@@ -21,6 +19,7 @@ import net.minecraft.util.NonNullList;
|
||||
import net.minecraftforge.items.wrapper.PlayerMainInvWrapper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PocketAPI implements ILuaAPI
|
||||
{
|
||||
@@ -49,14 +48,15 @@ public class PocketAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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 )
|
||||
{
|
||||
case 0:
|
||||
// equipBack
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
if( !(m_computer.getEntity() instanceof EntityPlayer) )
|
||||
{
|
||||
@@ -94,12 +94,12 @@ public class PocketAPI implements ILuaAPI
|
||||
// Set the new upgrade
|
||||
m_computer.setUpgrade( newUpgrade );
|
||||
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} );
|
||||
|
||||
case 1:
|
||||
// unequipBack
|
||||
return context.executeMainThreadTask( () ->
|
||||
return MethodResult.onMainThread( () ->
|
||||
{
|
||||
if( !(m_computer.getEntity() instanceof EntityPlayer) )
|
||||
{
|
||||
@@ -125,13 +125,21 @@ public class PocketAPI implements ILuaAPI
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return MethodResult.empty();
|
||||
} );
|
||||
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 )
|
||||
{
|
||||
for( int i = 0; i < inv.size(); i++ )
|
||||
|
@@ -5,7 +5,6 @@ import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.shared.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -76,8 +75,7 @@ public class PocketModem implements IPocketUpgrade
|
||||
modem.setLocation( entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ );
|
||||
}
|
||||
|
||||
ModemState state = modem.getModemState();
|
||||
if( state.pollChanged() ) access.setLight( state.isOpen() ? 0xBA0000 : -1 );
|
||||
access.setLight( modem.isActive() ? 0xBA0000 : -1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@
|
||||
package dan200.computercraft.shared.pocket.peripherals;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||
import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
@@ -20,7 +19,7 @@ public class PocketModemPeripheral extends WirelessModemPeripheral
|
||||
|
||||
public PocketModemPeripheral( boolean advanced )
|
||||
{
|
||||
super( new ModemState(), advanced );
|
||||
super( advanced );
|
||||
m_world = null;
|
||||
m_position = new Vec3d( 0.0, 0.0, 0.0 );
|
||||
}
|
||||
|
@@ -469,9 +469,9 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
|
||||
private void registerTileEntities()
|
||||
{
|
||||
// TileEntities
|
||||
GameRegistry.registerTileEntity( TileTurtle.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ) );
|
||||
GameRegistry.registerTileEntity( TileTurtleExpanded.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtleex" ) );
|
||||
GameRegistry.registerTileEntity( TileTurtleAdvanced.class, new ResourceLocation( ComputerCraft.MOD_ID, "turtleadv" ) );
|
||||
GameRegistry.registerTileEntity( TileTurtle.class, ComputerCraft.LOWER_ID + " : " + "turtle" );
|
||||
GameRegistry.registerTileEntity( TileTurtleExpanded.class, ComputerCraft.LOWER_ID + " : " + "turtleex" );
|
||||
GameRegistry.registerTileEntity( TileTurtleAdvanced.class, ComputerCraft.LOWER_ID + " : " + "turtleadv" );
|
||||
}
|
||||
|
||||
private void registerForgeHandlers()
|
||||
|
@@ -24,7 +24,6 @@ import dan200.computercraft.shared.computer.core.*;
|
||||
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
|
||||
import dan200.computercraft.shared.computer.items.ItemCommandComputer;
|
||||
import dan200.computercraft.shared.computer.items.ItemComputer;
|
||||
import dan200.computercraft.shared.datafix.Fixes;
|
||||
import dan200.computercraft.shared.integration.charset.IntegrationCharset;
|
||||
import dan200.computercraft.shared.media.common.DefaultMediaProvider;
|
||||
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.world.WorldEvent;
|
||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
import net.minecraftforge.fml.common.Loader;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
@@ -123,7 +121,6 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
|
||||
registerTileEntities();
|
||||
registerForgeHandlers();
|
||||
|
||||
Fixes.register( FMLCommonHandler.instance().getDataFixer() );
|
||||
if( Loader.isModLoaded( ModCharset.MODID ) ) IntegrationCharset.register();
|
||||
}
|
||||
|
||||
@@ -481,16 +478,16 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
|
||||
private void registerTileEntities()
|
||||
{
|
||||
// Tile Entities
|
||||
GameRegistry.registerTileEntity( TileComputer.class, new ResourceLocation( ComputerCraft.MOD_ID, "computer" ) );
|
||||
GameRegistry.registerTileEntity( TileDiskDrive.class, new ResourceLocation( ComputerCraft.MOD_ID, "diskdrive" ) );
|
||||
GameRegistry.registerTileEntity( TileWirelessModem.class, new ResourceLocation( ComputerCraft.MOD_ID, "wirelessmodem" ) );
|
||||
GameRegistry.registerTileEntity( TileMonitor.class, new ResourceLocation( ComputerCraft.MOD_ID, "monitor" ) );
|
||||
GameRegistry.registerTileEntity( TilePrinter.class, new ResourceLocation( ComputerCraft.MOD_ID, "ccprinter" ) );
|
||||
GameRegistry.registerTileEntity( TileCable.class, new ResourceLocation( ComputerCraft.MOD_ID, "wiredmodem" ) );
|
||||
GameRegistry.registerTileEntity( TileCommandComputer.class, new ResourceLocation( ComputerCraft.MOD_ID, "command_computer" ) );
|
||||
GameRegistry.registerTileEntity( TileAdvancedModem.class, new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) );
|
||||
GameRegistry.registerTileEntity( TileSpeaker.class, new ResourceLocation( ComputerCraft.MOD_ID, "speaker" ) );
|
||||
GameRegistry.registerTileEntity( TileWiredModemFull.class, new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) );
|
||||
GameRegistry.registerTileEntity( TileComputer.class, ComputerCraft.LOWER_ID + " : " + "computer" );
|
||||
GameRegistry.registerTileEntity( TileDiskDrive.class, ComputerCraft.LOWER_ID + " : " + "diskdrive" );
|
||||
GameRegistry.registerTileEntity( TileWirelessModem.class, ComputerCraft.LOWER_ID + " : " + "wirelessmodem" );
|
||||
GameRegistry.registerTileEntity( TileMonitor.class, ComputerCraft.LOWER_ID + " : " + "monitor" );
|
||||
GameRegistry.registerTileEntity( TilePrinter.class, ComputerCraft.LOWER_ID + " : " + "ccprinter" );
|
||||
GameRegistry.registerTileEntity( TileCable.class, ComputerCraft.LOWER_ID + " : " + "wiredmodem" );
|
||||
GameRegistry.registerTileEntity( TileCommandComputer.class, ComputerCraft.LOWER_ID + " : " + "command_computer" );
|
||||
GameRegistry.registerTileEntity( TileAdvancedModem.class, ComputerCraft.LOWER_ID + " : " + "advanced_modem" );
|
||||
GameRegistry.registerTileEntity( TileSpeaker.class, ComputerCraft.LOWER_ID + " : " + "speaker" );
|
||||
GameRegistry.registerTileEntity( TileWiredModemFull.class, ComputerCraft.LOWER_ID + " : " + "wired_modem_full" );
|
||||
|
||||
// Register peripheral providers
|
||||
ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() );
|
||||
|
@@ -6,9 +6,7 @@
|
||||
|
||||
package dan200.computercraft.shared.turtle.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleCommand;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
@@ -22,6 +20,7 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
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
|
||||
@@ -155,7 +154,8 @@ public class TurtleAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@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 )
|
||||
{
|
||||
@@ -163,89 +163,89 @@ public class TurtleAPI implements ILuaAPI
|
||||
{
|
||||
// forward
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Forward ) );
|
||||
return tryCommand( new TurtleMoveCommand( MoveDirection.Forward ) );
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// back
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Back ) );
|
||||
return tryCommand( new TurtleMoveCommand( MoveDirection.Back ) );
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// up
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Up ) );
|
||||
return tryCommand( new TurtleMoveCommand( MoveDirection.Up ) );
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// down
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleMoveCommand( MoveDirection.Down ) );
|
||||
return tryCommand( new TurtleMoveCommand( MoveDirection.Down ) );
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// turnLeft
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Left ) );
|
||||
return tryCommand( new TurtleTurnCommand( TurnDirection.Left ) );
|
||||
}
|
||||
case 5:
|
||||
{
|
||||
// turnRight
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleTurnCommand( TurnDirection.Right ) );
|
||||
return tryCommand( new TurtleTurnCommand( TurnDirection.Right ) );
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// dig
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDigCommand( InteractDirection.Forward, side ) );
|
||||
return tryCommand( new TurtleDigCommand( InteractDirection.Forward, side ) );
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
// digUp
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDigCommand( InteractDirection.Up, side ) );
|
||||
return tryCommand( new TurtleDigCommand( InteractDirection.Up, side ) );
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
// digDown
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDigCommand( InteractDirection.Down, side ) );
|
||||
return tryCommand( new TurtleDigCommand( InteractDirection.Down, side ) );
|
||||
}
|
||||
case 9:
|
||||
{
|
||||
// place
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Forward, args ) );
|
||||
return tryCommand( new TurtlePlaceCommand( InteractDirection.Forward, args ) );
|
||||
}
|
||||
case 10:
|
||||
{
|
||||
// placeUp
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Up, args ) );
|
||||
return tryCommand( new TurtlePlaceCommand( InteractDirection.Up, args ) );
|
||||
}
|
||||
case 11:
|
||||
{
|
||||
// placeDown
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Down, args ) );
|
||||
return tryCommand( new TurtlePlaceCommand( InteractDirection.Down, args ) );
|
||||
}
|
||||
case 12:
|
||||
{
|
||||
// drop
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDropCommand( InteractDirection.Forward, count ) );
|
||||
return tryCommand( new TurtleDropCommand( InteractDirection.Forward, count ) );
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
// select
|
||||
int slot = parseSlotNumber( args, 0 );
|
||||
return tryCommand( context, new TurtleSelectCommand( slot ) );
|
||||
return tryCommand( new TurtleSelectCommand( slot ) );
|
||||
}
|
||||
case 14:
|
||||
{
|
||||
@@ -254,11 +254,11 @@ public class TurtleAPI implements ILuaAPI
|
||||
ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
|
||||
if( !stack.isEmpty() )
|
||||
{
|
||||
return new Object[] { stack.getCount() };
|
||||
return MethodResult.of( stack.getCount() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Object[] { 0 };
|
||||
return MethodResult.of( 0 );
|
||||
}
|
||||
}
|
||||
case 15:
|
||||
@@ -268,172 +268,172 @@ public class TurtleAPI implements ILuaAPI
|
||||
ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
|
||||
if( !stack.isEmpty() )
|
||||
{
|
||||
return new Object[] {
|
||||
return MethodResult.of(
|
||||
Math.min( stack.getMaxStackSize(), 64 ) - stack.getCount()
|
||||
};
|
||||
);
|
||||
}
|
||||
return new Object[] { 64 };
|
||||
return MethodResult.of( 64 );
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
// detect
|
||||
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Forward ) );
|
||||
return tryCommand( new TurtleDetectCommand( InteractDirection.Forward ) );
|
||||
}
|
||||
case 17:
|
||||
{
|
||||
// detectUp
|
||||
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Up ) );
|
||||
return tryCommand( new TurtleDetectCommand( InteractDirection.Up ) );
|
||||
}
|
||||
case 18:
|
||||
{
|
||||
// detectDown
|
||||
return tryCommand( context, new TurtleDetectCommand( InteractDirection.Down ) );
|
||||
return tryCommand( new TurtleDetectCommand( InteractDirection.Down ) );
|
||||
}
|
||||
case 19:
|
||||
{
|
||||
// compare
|
||||
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Forward ) );
|
||||
return tryCommand( new TurtleCompareCommand( InteractDirection.Forward ) );
|
||||
}
|
||||
case 20:
|
||||
{
|
||||
// compareUp
|
||||
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Up ) );
|
||||
return tryCommand( new TurtleCompareCommand( InteractDirection.Up ) );
|
||||
}
|
||||
case 21:
|
||||
{
|
||||
// compareDown
|
||||
return tryCommand( context, new TurtleCompareCommand( InteractDirection.Down ) );
|
||||
return tryCommand( new TurtleCompareCommand( InteractDirection.Down ) );
|
||||
}
|
||||
case 22:
|
||||
{
|
||||
// attack
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Forward, side ) );
|
||||
return tryCommand( new TurtleAttackCommand( InteractDirection.Forward, side ) );
|
||||
}
|
||||
case 23:
|
||||
{
|
||||
// attackUp
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Up, side ) );
|
||||
return tryCommand( new TurtleAttackCommand( InteractDirection.Up, side ) );
|
||||
}
|
||||
case 24:
|
||||
{
|
||||
// attackDown
|
||||
Optional<TurtleSide> side = parseSide( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleAttackCommand( InteractDirection.Down, side ) );
|
||||
return tryCommand( new TurtleAttackCommand( InteractDirection.Down, side ) );
|
||||
}
|
||||
case 25:
|
||||
{
|
||||
// dropUp
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDropCommand( InteractDirection.Up, count ) );
|
||||
return tryCommand( new TurtleDropCommand( InteractDirection.Up, count ) );
|
||||
}
|
||||
case 26:
|
||||
{
|
||||
// dropDown
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleDropCommand( InteractDirection.Down, count ) );
|
||||
return tryCommand( new TurtleDropCommand( InteractDirection.Down, count ) );
|
||||
}
|
||||
case 27:
|
||||
{
|
||||
// suck
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Forward, count ) );
|
||||
return tryCommand( new TurtleSuckCommand( InteractDirection.Forward, count ) );
|
||||
}
|
||||
case 28:
|
||||
{
|
||||
// suckUp
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Up, count ) );
|
||||
return tryCommand( new TurtleSuckCommand( InteractDirection.Up, count ) );
|
||||
}
|
||||
case 29:
|
||||
{
|
||||
// suckDown
|
||||
int count = parseCount( args, 0 );
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleSuckCommand( InteractDirection.Down, count ) );
|
||||
return tryCommand( new TurtleSuckCommand( InteractDirection.Down, count ) );
|
||||
}
|
||||
case 30:
|
||||
{
|
||||
// getFuelLevel
|
||||
if( m_turtle.isFuelNeeded() )
|
||||
{
|
||||
return new Object[] { m_turtle.getFuelLevel() };
|
||||
return MethodResult.of( m_turtle.getFuelLevel() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Object[] { "unlimited" };
|
||||
return MethodResult.of( "unlimited" );
|
||||
}
|
||||
}
|
||||
case 31:
|
||||
{
|
||||
// refuel
|
||||
int count = parseCount( args, 0 );
|
||||
return tryCommand( context, new TurtleRefuelCommand( count ) );
|
||||
return tryCommand( new TurtleRefuelCommand( count ) );
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
// compareTo
|
||||
int slot = parseSlotNumber( args, 0 );
|
||||
return tryCommand( context, new TurtleCompareToCommand( slot ) );
|
||||
return tryCommand( new TurtleCompareToCommand( slot ) );
|
||||
}
|
||||
case 33:
|
||||
{
|
||||
// transferTo
|
||||
int slot = parseSlotNumber( args, 0 );
|
||||
int count = parseCount( args, 1 );
|
||||
return tryCommand( context, new TurtleTransferToCommand( slot, count ) );
|
||||
return tryCommand( new TurtleTransferToCommand( slot, count ) );
|
||||
}
|
||||
case 34:
|
||||
{
|
||||
// getSelectedSlot
|
||||
return new Object[] { m_turtle.getSelectedSlot() + 1 };
|
||||
return MethodResult.of( m_turtle.getSelectedSlot() + 1 );
|
||||
}
|
||||
case 35:
|
||||
{
|
||||
// getFuelLimit
|
||||
if( m_turtle.isFuelNeeded() )
|
||||
{
|
||||
return new Object[] { m_turtle.getFuelLimit() };
|
||||
return MethodResult.of( m_turtle.getFuelLimit() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Object[] { "unlimited" };
|
||||
return MethodResult.of( "unlimited" );
|
||||
}
|
||||
}
|
||||
case 36:
|
||||
{
|
||||
// equipLeft
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Left ) );
|
||||
return tryCommand( new TurtleEquipCommand( TurtleSide.Left ) );
|
||||
}
|
||||
case 37:
|
||||
{
|
||||
// equipRight
|
||||
m_environment.addTrackingChange( TrackingField.TURTLE_OPS );
|
||||
return tryCommand( context, new TurtleEquipCommand( TurtleSide.Right ) );
|
||||
return tryCommand( new TurtleEquipCommand( TurtleSide.Right ) );
|
||||
}
|
||||
case 38:
|
||||
{
|
||||
// inspect
|
||||
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Forward ) );
|
||||
return tryCommand( new TurtleInspectCommand( InteractDirection.Forward ) );
|
||||
}
|
||||
case 39:
|
||||
{
|
||||
// inspectUp
|
||||
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) );
|
||||
return tryCommand( new TurtleInspectCommand( InteractDirection.Up ) );
|
||||
}
|
||||
case 40:
|
||||
{
|
||||
// inspectDown
|
||||
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) );
|
||||
return tryCommand( new TurtleInspectCommand( InteractDirection.Down ) );
|
||||
}
|
||||
case 41:
|
||||
{
|
||||
@@ -455,20 +455,28 @@ public class TurtleAPI implements ILuaAPI
|
||||
TurtleActionEvent event = new TurtleActionEvent( m_turtle, TurtleAction.INSPECT_ITEM );
|
||||
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
|
||||
{
|
||||
return new Object[] { null };
|
||||
return MethodResult.of( new Object[] { null } );
|
||||
}
|
||||
}
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,9 @@ import com.google.common.base.Objects;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaFunction;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.*;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
|
||||
@@ -761,6 +763,7 @@ public class TurtleBrain implements ITurtleAccess
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException
|
||||
{
|
||||
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
|
||||
public void playAnimation( @Nonnull TurtleAnimation animation )
|
||||
{
|
||||
|
@@ -6,19 +6,21 @@
|
||||
|
||||
package dan200.computercraft.shared.turtle.upgrades;
|
||||
|
||||
import dan200.computercraft.api.lua.ICallContext;
|
||||
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.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleCraftCommand;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
|
||||
|
||||
public class CraftingTablePeripheral
|
||||
implements IPeripheral
|
||||
public class CraftingTablePeripheral implements IPeripheral
|
||||
{
|
||||
private final ITurtleAccess m_turtle;
|
||||
|
||||
@@ -26,7 +28,7 @@ public class CraftingTablePeripheral
|
||||
{
|
||||
m_turtle = turtle;
|
||||
}
|
||||
|
||||
|
||||
// IPeripheral implementation
|
||||
|
||||
@Nonnull
|
||||
@@ -35,7 +37,7 @@ public class CraftingTablePeripheral
|
||||
{
|
||||
return "workbench";
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
@@ -44,7 +46,7 @@ public class CraftingTablePeripheral
|
||||
"craft",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private int parseCount( Object[] arguments ) throws LuaException
|
||||
{
|
||||
int count = optInt( arguments, 0, 64 );
|
||||
@@ -54,9 +56,10 @@ public class CraftingTablePeripheral
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
@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 )
|
||||
{
|
||||
@@ -64,15 +67,24 @@ public class CraftingTablePeripheral
|
||||
{
|
||||
// craft
|
||||
final int limit = parseCount( arguments );
|
||||
return m_turtle.executeCommand( context, new TurtleCraftCommand( limit ) );
|
||||
return m_turtle.executeCommand( new TurtleCraftCommand( limit ) );
|
||||
}
|
||||
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
|
||||
public boolean equals( IPeripheral other )
|
||||
{
|
||||
|
@@ -10,7 +10,6 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.*;
|
||||
import dan200.computercraft.shared.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.peripheral.common.PeripheralItemFactory;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||
import dan200.computercraft.shared.peripheral.modem.WirelessModemPeripheral;
|
||||
import net.minecraft.client.Minecraft;
|
||||
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.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
@@ -38,7 +37,7 @@ public class TurtleModem implements ITurtleUpgrade
|
||||
|
||||
public Peripheral( ITurtleAccess turtle, boolean advanced )
|
||||
{
|
||||
super( new ModemState(), advanced );
|
||||
super( advanced );
|
||||
m_turtle = turtle;
|
||||
}
|
||||
|
||||
@@ -222,12 +221,12 @@ public class TurtleModem implements ITurtleUpgrade
|
||||
if( !turtle.getWorld().isRemote )
|
||||
{
|
||||
IPeripheral peripheral = turtle.getPeripheral( side );
|
||||
if( peripheral instanceof Peripheral )
|
||||
if( peripheral != null && peripheral instanceof Peripheral )
|
||||
{
|
||||
ModemState state = ((Peripheral) peripheral).getModemState();
|
||||
if( state.pollChanged() )
|
||||
Peripheral modemPeripheral = (Peripheral)peripheral;
|
||||
if( modemPeripheral.pollChanged() )
|
||||
{
|
||||
turtle.getUpgradeNBTData( side ).setBoolean( "active", state.isOpen() );
|
||||
turtle.getUpgradeNBTData( side ).setBoolean( "active", modemPeripheral.isActive() );
|
||||
turtle.updateUpgradeNBTData( side );
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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
|
@@ -1,249 +1,202 @@
|
||||
-- Definition for the IO API
|
||||
local typeOf = _G.type
|
||||
|
||||
--- If we return nil then close the file, as we've reached the end.
|
||||
-- We use this weird wrapper function as we wish to preserve the varargs
|
||||
local function checkResult(handle, ...)
|
||||
if ... == nil and handle._autoclose and not handle._closed then handle:close() end
|
||||
return ...
|
||||
end
|
||||
|
||||
local handleMetatable
|
||||
handleMetatable = {
|
||||
__name = "FILE*",
|
||||
__tostring = function(self)
|
||||
if self._closed then
|
||||
return "file (closed)"
|
||||
else
|
||||
local hash = tostring(self._handle):match("table: (%x+)")
|
||||
return "file (" .. hash .. ")"
|
||||
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 g_defaultInput = {
|
||||
bFileHandle = true,
|
||||
bClosed = false,
|
||||
close = function( self )
|
||||
end,
|
||||
read = function( self, _sFormat )
|
||||
if _sFormat and _sFormat ~= "*l" then
|
||||
error( "Unsupported format" )
|
||||
end
|
||||
return _G.read()
|
||||
end,
|
||||
lines = function( self )
|
||||
return function()
|
||||
return _G.read()
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
local defaultInput = setmetatable({
|
||||
_handle = { readLine = _G.read }
|
||||
}, handleMetatable)
|
||||
local g_defaultOutput = {
|
||||
bFileHandle = true,
|
||||
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({
|
||||
_handle = { write = _G.write }
|
||||
}, handleMetatable)
|
||||
local g_currentInput = g_defaultInput
|
||||
local g_currentOutput = g_defaultOutput
|
||||
|
||||
local defaultError = setmetatable({
|
||||
_handle = {
|
||||
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()
|
||||
function close( _file )
|
||||
(_file or g_currentOutput):close()
|
||||
end
|
||||
|
||||
function flush()
|
||||
return currentOutput:flush()
|
||||
g_currentOutput:flush()
|
||||
end
|
||||
|
||||
function input(_arg)
|
||||
if typeOf(_arg) == "string" then
|
||||
local res, err = open(_arg, "rb")
|
||||
if not res then error(err, 2) end
|
||||
currentInput = res
|
||||
elseif typeOf(_arg) == "table" and getmetatable(_arg) == handleMetatable then
|
||||
currentInput = _arg
|
||||
elseif _arg ~= nil then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(_arg) .. ")", 2)
|
||||
function input( _arg )
|
||||
if _G.type( _arg ) == "string" then
|
||||
g_currentInput = open( _arg, "r" )
|
||||
elseif _G.type( _arg ) == "table" then
|
||||
g_currentInput = _arg
|
||||
elseif _G.type( _arg ) == "nil" then
|
||||
return g_currentInput
|
||||
else
|
||||
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
|
||||
|
||||
return currentInput
|
||||
if _sFileName then
|
||||
return open( _sFileName, "r" ):lines()
|
||||
else
|
||||
return g_currentInput:lines()
|
||||
end
|
||||
end
|
||||
|
||||
function lines(_sFileName)
|
||||
if _sFileName ~= nil and typeOf(_sFileName) ~= "string" then
|
||||
error("bad argument #1 (expected string, got " .. typeOf(_sFileName) .. ")", 2)
|
||||
function open( _sPath, _sMode )
|
||||
if _G.type( _sPath ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. _G.type( _sPath ) .. ")", 2 )
|
||||
end
|
||||
if _sFileName then
|
||||
local ok, err = open(_sFileName, "rb")
|
||||
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()
|
||||
if _sMode ~= nil and _G.type( _sMode ) ~= "string" then
|
||||
error( "bad argument #2 (expected string, got " .. _G.type( _sMode ) .. ")", 2 )
|
||||
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
|
||||
|
||||
function open(_sPath, _sMode)
|
||||
if typeOf(_sPath) ~= "string" then
|
||||
error("bad argument #1 (expected string, got " .. typeOf(_sPath) .. ")", 2)
|
||||
end
|
||||
if _sMode ~= nil and typeOf(_sMode) ~= "string" then
|
||||
error("bad argument #2 (expected string, got " .. typeOf(_sMode) .. ")", 2)
|
||||
end
|
||||
|
||||
local sMode = _sMode and _sMode:gsub("%+", "") or "rb"
|
||||
local file, err = fs.open(_sPath, sMode)
|
||||
if not file then return nil, err end
|
||||
|
||||
return setmetatable({ _handle = file }, handleMetatable)
|
||||
function output( _arg )
|
||||
if _G.type( _arg ) == "string" then
|
||||
g_currentOutput = open( _arg, "w" )
|
||||
elseif _G.type( _arg ) == "table" then
|
||||
g_currentOutput = _arg
|
||||
elseif _G.type( _arg ) == "nil" then
|
||||
return g_currentOutput
|
||||
else
|
||||
error( "bad argument #1 (expected string/table/nil, got " .. _G.type( _arg ) .. ")", 2 )
|
||||
end
|
||||
end
|
||||
|
||||
function output(_arg)
|
||||
if typeOf(_arg) == "string" then
|
||||
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
|
||||
function read( ... )
|
||||
return input():read( ... )
|
||||
end
|
||||
|
||||
function read(...)
|
||||
return currentInput:read(...)
|
||||
function type( _handle )
|
||||
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
|
||||
|
||||
function type(handle)
|
||||
if typeOf(handle) == "table" and getmetatable(handle) == handleMetatable then
|
||||
if handle._closed then
|
||||
return "closed file"
|
||||
else
|
||||
return "file"
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function write(...)
|
||||
return currentOutput:write(...)
|
||||
function write( ... )
|
||||
return output():write( ... )
|
||||
end
|
||||
|
@@ -258,10 +258,6 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
end
|
||||
|
||||
function window.getCursorBlink()
|
||||
return bCursorBlink
|
||||
end
|
||||
|
||||
local function isColor()
|
||||
return parent.isColor()
|
||||
end
|
||||
|
@@ -44,7 +44,7 @@ local ok, error = pcall( function()
|
||||
if sEvent == "modem_message" then
|
||||
-- Got a modem message, rebroadcast it if it's a rednet thing
|
||||
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
|
||||
-- Ensure we only repeat a message once
|
||||
tReceivedMessages[ tMessage.nMessageID ] = true
|
||||
|
@@ -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 ) );
|
||||
}
|
||||
}
|
@@ -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" );
|
||||
}
|
||||
}
|
@@ -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 + ")";
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
Reference in New Issue
Block a user