1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 14:37:39 +00:00

Compare commits

...

30 Commits

Author SHA1 Message Date
SquidDev
8ae65d1bdd A couple of minor cleanups on the last commit
- Remove a redundant logger
 - Provide a getter for the ComputerCraft thread group. This allows us
   to monitor child threads within prometheus.
 - Replace a deprecated call with a fastutils alternative.
2018-11-22 12:48:06 +00:00
SquidDev
0829506176 Remove last usage of Trove
Minecraft uses it internally, so we can rely on this always being
around. I do not belive Trove exists within 1.13.
2018-11-22 12:15:55 +00:00
SquidDev
71ee692da0 Some more improvements to threading
- Put all ComputerCraft threads into a thread group
 - Be a little more aggressive in when we cleanup/abandon threads.
2018-11-22 12:09:24 +00:00
SquidDev
acd0092ed5 Add back JarMount for backwards compatibility
Some mods (*cough* Computronics *cough*) directly access this class,
rather than using the API. We add this back to ensure they still behave
as expected.

Truth be told, I can't really complain, as Plethora also does dodgy
thing with CC internals.
2018-11-22 10:06:43 +00:00
SquidDev
1160ffbf9e Bump version and require a minimum version of Forge 2018-11-21 18:11:14 +00:00
SquidDev
93cb6547bd Improvements for coroutine creation
- Keep track of the number of created and destroyed coroutines for each
   computer.
 - Run coroutines with a thread pool executor, which will keep stale
   threads around for 60 seconds. This substantially reduces the
   pressure from short-lived coroutines.
 - Update to the latest Cobalt version.
2018-11-21 16:47:06 +00:00
SquidDev
f9c91f288f Try to remove some locking
Ideally we would be able to avoid locking at all on the main thread, but
needs must.
2018-11-19 20:56:44 +00:00
SquidDev
62cf921cc6 Allow modems to handle being attached to multiple computers
- Introduce a ModemState, which shares the open channels across all
   modem instances of a wired modem.
 - Keep a set of computers for all modem peripherals.
 - Keep a map of computers -> (string, peripheral) for wired modem
   peripherals. We shouldn't need this one, as you cannot attach one
   modem to another, but it's good to be consistent.

One major change here is that modems will continue to be "on", even if
no computers are attached. This would substantially increase
implementation complexity, so I think this is an acceptable compromise
for now.

Should fix #74
2018-11-17 11:52:12 +00:00
SquidDev
4bd7381827 Add support for Forge's ISelectiveResourceReloadListener
This means we don't reload turtles every time someone just reloads
shaders or the language pack.
2018-11-17 10:54:42 +00:00
SquidDev
43459ec825 Fix read methods failing on malformed unicode.
Java configured the charset decoders/encoders for streams to REPLACE
malformed characters rather than the default REPORT. It does not do the
same for channels, and so we were catching an IO exception and returning
null.
2018-11-16 20:58:49 +00:00
SquidDev
5fa01f8b96 Truncate files when writing them
This is performed by default, but as we don't pass any options, this
flag is removed.

Closes #75
2018-11-16 13:09:12 +00:00
SquidDev
67d5693d2a Fix tile entities being registered with incorrect names
We'd somehow added spaces, which means they weren't registered under the
computercraft domain (rather, the "computercraft " one). We also create
a datafixer to ensure old worlds are handled correctly.
2018-11-16 12:29:29 +00:00
SquidDev
c2a782afa4 Remove some old scripts
These were part of the original CC repo, but aren't really needed now
that everything important (LuaJ, bundling API docs+source), has been
moved to Gradle.

We also remove the LuaJ lib. It's sad to see it go, but it was rather
redundant. We're keeping the LuaJ sources for now, as I don't really
want a really large diff.
2018-11-16 11:43:42 +00:00
SquidDev
14c9558ee6 Bump version 2018-11-03 15:44:36 +00:00
SquidDev
d53a73e7e7 Fix resource handle loading
See #70
2018-10-29 15:59:43 +00:00
SquidDev
7e334bd4a5 Fix a couple of other bugs with the fs rewrite
- Fix stdin not being considered a "readable" input
 - Return an unsigned byte rather than a signed one for no-args .read()
2018-10-28 08:39:44 +00:00
SquidDev
51e787f631 Bump version and update feature list 2018-10-24 12:49:35 +01:00
SquidDev
8080699030 Couple of minor improvements to the alternative mount implementation
- Rename openStreamFor* methods to more accurate openChannelFor*
 - Fix ArrayByteChannel having an incorrect .position() implementation

Cherry-picked from the PR against dan200/ComputerCraft
2018-10-24 12:39:22 +01:00
SquidDev
e555f9f7f0 Merge pull request #575 from SquidDev-CC/ComputerCraft/feature/file-seeking
Rewrite file systems to use ByteChannels
2018-10-24 12:20:53 +01:00
SquidDev
822db6e9b5 Add support for binary websockets
- Add an argument to send which controls whether it's a binary message
   or not. This is a little ugly, but it's probably more effective than
   anything else.
 - Fix binary frames not correctly queueing the correct data in the
   message event.

Closes #69
2018-10-23 12:15:40 +01:00
SquidDev
ac1f30ef43 Clamp the volume for all sounds, rather than just notes
This restores the previous behaviour.
2018-10-12 12:05:31 +01:00
SquidDev
0fc1b8c46b Merge pull request #578 from SquidDev-CC/ComputerCraft/feature/speaker-sound
Change speakers to use the SPacketCustomSound packet instead
2018-10-10 16:57:34 +01:00
SquidDev
e7c19bcf55 Change speakers to use the SPacketCustomSound packet instead
The method to register new SoundEvents is private, which means that few
(if any) mods actually register them. Consequently, one could not use
the speaker to play any modded sound, as they weren't registered on the
server side.

Using SPacketCustomSound does mean we can no longer determine if a sound
exists or not, but I think a price I'm willing to pay in order to allow
playing modded sounds.
2018-10-10 08:46:30 +01:00
SquidDev
63ca8aca4c Merge pull request #68 from Vexatos/patch-1
Create de_de.lang
2018-10-02 16:47:42 +01:00
Vexatos
2caa9c57fc Create de_de.lang 2018-10-02 15:43:43 +02:00
SquidDev
33fad2da15 Merge pull request #577 from SquidDev-CC/ComputerCraft/feature/get-blink
Add .getCursorBlink to monitors and terminals
2018-09-28 16:23:00 +01:00
SquidDev
518eefbe10 Rewrite file systems to use ByteChannels
This replaces the existing IMount openFor* method with openChannelFor*
ones, which return an appropriate byte channel instead.

As channels are not correctly closed when GCed, we introduce a
FileSystemWrapper. We store a weak reference to this, and when it is
GCed or the file closed, we will remove it from our "open file" set and
ensure any underlying buffers are closed.

While this change may seem a little odd, it does introduce some
benefits:

 - We can replace JarMount with a more general FileSystemMount. This
   does assume a read-only file system, but could technically be used
   for other sources.

 - Add support for seekable (binary) handles. We can now look for
   instances of SeekableByteChannel and dynamically add it. This works
   for all binary filesystem and HTTP streams.

 - Rewrite the io library to more accurately emulate PUC Lua's
   implementation. We do not correctly implement some elements (most
   noticably "*n", but it's a definite improvement.
2018-09-26 10:00:17 +01:00
SquidDev
1ba73454c1 Add .getCursorBlink to monitors and terminals
Closes #576
2018-09-23 09:34:28 +01:00
SquidDev
ee4735c17c Merge pull request #573 from osmarks/ComputerCraft/patch-1
Fix a crash in rednet `repeat`
2018-09-09 17:49:24 +01:00
Oliver Marks
b008edae90 Fix a crash in rednet repeat
This crash can be triggered remotely by specially constructed rednet messages, making this a bit of a problem, as any repeaters can be remotely crashed.
2018-09-08 21:55:36 +01:00
66 changed files with 2295 additions and 1379 deletions

12
.gitignore vendored
View File

@@ -1,12 +1,12 @@
build
out
run
deploy
/build
/out
/run
*.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

View File

@@ -28,6 +28,8 @@ 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,
@@ -36,7 +38,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 instead.
code, do consider submitting it to the ComputerCraft repository first.
That being said, in order to start helping develop CC:T, you'll need to follow these steps:

View File

@@ -23,7 +23,7 @@ apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
version = "1.80pr1.8"
version = "1.80pr1.11"
group = "org.squiddev"
archivesBaseName = "cc-tweaked"
@@ -66,7 +66,7 @@ dependencies {
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
shade 'org.squiddev:Cobalt:0.3.2'
shade 'org.squiddev:Cobalt:0.4.0'
testCompile 'junit:junit:4.11'
@@ -131,7 +131,7 @@ curseforge {
project {
id = '282001'
releaseType = 'beta'
changelog = ''
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${project.version})."
}
}

View File

@@ -1,12 +0,0 @@
#!/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 ..

View File

@@ -1,10 +0,0 @@
#!/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

View File

@@ -1,21 +0,0 @@
#!/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."

Binary file not shown.

View File

@@ -1,7 +0,0 @@
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

View File

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

View File

@@ -26,11 +26,9 @@ 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.JarMount;
import dan200.computercraft.core.filesystem.FileSystemMount;
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;
@@ -60,7 +58,10 @@ 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.*;
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.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled;
@@ -97,6 +98,8 @@ 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;
@@ -108,12 +111,12 @@ import java.util.zip.ZipFile;
@Mod(
modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory"
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory",
dependencies = "required:forge@[14.23.4.2746,)"
)
public class ComputerCraft
{
public static final String MOD_ID = "computercraft";
public static final String LOWER_ID = "computercraft";
// GUI IDs
public static final int diskDriveGUIID = 100;
@@ -922,11 +925,12 @@ public class ComputerCraft
{
try
{
IMount jarMount = new JarMount( modJar, subPath );
mounts.add( jarMount );
FileSystem fs = FileSystems.newFileSystem( modJar.toPath(), ComputerCraft.class.getClassLoader() );
mounts.add( new FileSystemMount( fs, subPath ) );
}
catch( IOException e )
catch( IOException | RuntimeException | ServiceConfigurationError e )
{
ComputerCraft.log.error( "Could not load mount from mod jar", e );
// Ignore
}
}
@@ -936,16 +940,16 @@ public class ComputerCraft
if( resourcePackDir.exists() && resourcePackDir.isDirectory() )
{
String[] resourcePacks = resourcePackDir.list();
for( String resourcePack1 : resourcePacks )
for( String resourcePackName : resourcePacks )
{
try
{
File resourcePack = new File( resourcePackDir, resourcePack1 );
File resourcePack = new File( resourcePackDir, resourcePackName );
if( !resourcePack.isDirectory() )
{
// Mount a resource pack from a jar
IMount resourcePackMount = new JarMount( resourcePack, subPath );
mounts.add( resourcePackMount );
FileSystem fs = FileSystems.newFileSystem( resourcePack.toPath(), ComputerCraft.class.getClassLoader() );
mounts.add( new FileSystemMount( fs, subPath ) );
}
else
{
@@ -958,9 +962,9 @@ public class ComputerCraft
}
}
}
catch( IOException e )
catch( IOException | RuntimeException | ServiceConfigurationError e )
{
// Ignore
ComputerCraft.log.error( "Could not load resource pack '" + resourcePackName + "'", e );
}
}
}

View File

@@ -13,6 +13,8 @@ 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;
/**
@@ -72,7 +74,25 @@ 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 ) );
}
}

View File

@@ -13,6 +13,8 @@ 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)}
@@ -50,20 +52,54 @@ 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.

View File

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

View File

@@ -18,12 +18,14 @@ 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;
@@ -32,8 +34,9 @@ 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, IResourceManagerReloadListener
public class TurtleSmartItemModel implements IBakedModel, ISelectiveResourceReloadListener
{
private static final Matrix4f s_identity, s_flip;
@@ -155,9 +158,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
}
@Override
public void onResourceManagerReload( @Nonnull IResourceManager resourceManager )
public void onResourceManagerReload( @Nonnull IResourceManager resourceManager, @Nonnull Predicate<IResourceType> resourcePredicate )
{
m_cachedModels.clear();
if( resourcePredicate.test( VanillaResourceType.MODELS ) ) m_cachedModels.clear();
}
private IBakedModel buildModel( TurtleModelCombination combo )

View File

@@ -9,19 +9,23 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
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.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.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.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
@@ -221,38 +225,38 @@ public class FSAPI implements ILuaAPI
case "r":
{
// Open the file for reading, then create a wrapper around the reader
InputStream reader = m_fileSystem.openForRead( path );
return new Object[] { new EncodedInputHandle( reader ) };
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
}
case "w":
{
// Open the file for writing, then create a wrapper around the writer
OutputStream writer = m_fileSystem.openForWrite( path, false );
return new Object[] { new EncodedOutputHandle( writer ) };
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "a":
{
// Open the file for appending, then create a wrapper around the writer
OutputStream writer = m_fileSystem.openForWrite( path, true );
return new Object[] { new EncodedOutputHandle( writer ) };
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "rb":
{
// Open the file for binary reading, then create a wrapper around the reader
InputStream reader = m_fileSystem.openForRead( path );
return new Object[] { new BinaryInputHandle( reader ) };
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
}
case "wb":
{
// Open the file for binary writing, then create a wrapper around the writer
OutputStream writer = m_fileSystem.openForWrite( path, false );
return new Object[] { new BinaryOutputHandle( writer ) };
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
case "ab":
{
// Open the file for binary appending, then create a wrapper around the reader
OutputStream writer = m_fileSystem.openForWrite( path, true );
return new Object[] { new BinaryOutputHandle( writer ) };
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
default:
throw new LuaException( "Unsupported mode" );

View File

@@ -64,7 +64,8 @@ public class TermAPI implements ILuaAPI
"setPaletteColour",
"setPaletteColor",
"getPaletteColour",
"getPaletteColor"
"getPaletteColor",
"getCursorBlink",
};
}
@@ -284,6 +285,9 @@ public class TermAPI implements ILuaAPI
}
return null;
}
case 23:
// getCursorBlink
return new Object[] { m_terminal.getCursorBlink() };
default:
{
return null;

View File

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

View File

@@ -1,122 +0,0 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.io.ByteStreams;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
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",
};
}
@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 )
{
// 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 null;
if( read < count ) bytes = Arrays.copyOf( bytes, read );
return new Object[] { 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 new Object[] { out.toByteArray() };
}
}
else
{
int b = m_stream.read();
return b == -1 ? null : new Object[] { b };
}
}
catch( IOException e )
{
return null;
}
case 1:
// readAll
checkOpen();
try
{
byte[] out = ByteStreams.toByteArray( m_stream );
return out == null ? null : new Object[] { out };
}
catch( IOException e )
{
return null;
}
case 2:
//close
close();
return null;
default:
return null;
}
}
}

View File

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

View File

@@ -1,33 +1,45 @@
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.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
public class BinaryOutputHandle extends HandleGeneric
public class BinaryWritableHandle extends HandleGeneric
{
private final OutputStream m_writer;
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 );
public BinaryOutputHandle( OutputStream writer )
private final WritableByteChannel m_writer;
private final SeekableByteChannel m_seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
{
super( writer );
this.m_writer = writer;
super( closeable );
this.m_writer = channel;
this.m_seekable = asSeekable( channel );
}
public BinaryWritableHandle( WritableByteChannel channel )
{
this( channel, channel );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"write",
"flush",
"close",
};
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
}
@Override
@@ -43,12 +55,16 @@ public class BinaryOutputHandle extends HandleGeneric
if( args.length > 0 && args[ 0 ] instanceof Number )
{
int number = ((Number) args[ 0 ]).intValue();
m_writer.write( number );
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( StringUtil.encodeString( value ) );
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
}
else
{
@@ -65,7 +81,9 @@ public class BinaryOutputHandle extends HandleGeneric
checkOpen();
try
{
m_writer.flush();
// Technically this is not needed
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
return null;
}
catch( IOException e )
@@ -76,6 +94,10 @@ public class BinaryOutputHandle extends HandleGeneric
//close
close();
return null;
case 3:
// seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
}

View File

@@ -4,45 +4,34 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.*;
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 static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class EncodedInputHandle extends HandleGeneric
public class EncodedReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final BufferedReader m_reader;
private BufferedReader m_reader;
public EncodedInputHandle( BufferedReader reader )
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
{
super( reader );
super( closable );
this.m_reader = reader;
}
public EncodedInputHandle( InputStream stream )
public EncodedReadableHandle( @Nonnull BufferedReader 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 );
this( reader, reader );
}
@Nonnull
@@ -63,13 +52,17 @@ public class EncodedInputHandle extends HandleGeneric
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 };
}
else
@@ -81,6 +74,7 @@ public class EncodedInputHandle extends HandleGeneric
{
return null;
}
}
case 1:
// readAll
checkOpen();
@@ -108,7 +102,6 @@ public class EncodedInputHandle extends HandleGeneric
close();
return null;
case 3:
// read
checkOpen();
try
{
@@ -161,4 +154,19 @@ public class EncodedInputHandle extends HandleGeneric
return null;
}
}
public static BufferedReader openUtf8( ReadableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedReader open( ReadableByteChannel channel, Charset charset )
{
// Create a charset decoder with the same properties as StreamDecoder does for
// InputStreams: namely, replace everything instead of erroring.
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput( CodingErrorAction.REPLACE )
.onUnmappableCharacter( CodingErrorAction.REPLACE );
return new BufferedReader( Channels.newReader( channel, decoder, -1 ) );
}
}

View File

@@ -4,41 +4,29 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.*;
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;
public class EncodedOutputHandle extends HandleGeneric
public class EncodedWritableHandle extends HandleGeneric
{
private final BufferedWriter m_writer;
private BufferedWriter m_writer;
public EncodedOutputHandle( BufferedWriter writer )
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
{
super( writer );
super( closable );
this.m_writer = writer;
}
public EncodedOutputHandle( OutputStream stream )
public EncodedWritableHandle( @Nonnull BufferedWriter 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 );
this( writer, writer );
}
@Nonnull
@@ -125,4 +113,20 @@ public class EncodedOutputHandle extends HandleGeneric
return null;
}
}
public static BufferedWriter openUtf8( WritableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedWriter open( WritableByteChannel channel, Charset charset )
{
// Create a charset encoder with the same properties as StreamEncoder does for
// OutputStreams: namely, replace everything instead of erroring.
CharsetEncoder encoder = charset.newEncoder()
.onMalformedInput( CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return new BufferedWriter( Channels.newWriter( channel, encoder, -1 ) );
}
}

View File

@@ -3,17 +3,23 @@ package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
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
{
protected final Closeable m_closable;
protected boolean m_open = true;
private Closeable m_closable;
private boolean m_open = true;
public HandleGeneric( Closeable m_closable )
protected HandleGeneric( @Nonnull Closeable closable )
{
this.m_closable = m_closable;
this.m_closable = closable;
}
protected void checkOpen() throws LuaException
@@ -21,7 +27,7 @@ public abstract class HandleGeneric implements ILuaObject
if( !m_open ) throw new LuaException( "attempt to use a closed file" );
}
protected void close()
protected final void close()
{
try
{
@@ -31,5 +37,64 @@ 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
{
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;
}
}
}

View File

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

View File

@@ -13,13 +13,17 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
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.tracking.TrackingField;
import javax.annotation.Nonnull;
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;
@@ -182,6 +186,10 @@ 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<>();
@@ -193,9 +201,11 @@ public class HTTPRequest implements Runnable
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
getHeaderSize( connection.getHeaderFields() ) + result.length );
InputStream contents = new ByteArrayInputStream( result );
SeekableByteChannel contents = new ArrayByteChannel( result );
ILuaObject stream = wrapStream(
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
m_binary
? new BinaryReadableHandle( contents )
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, charset ) ),
connection.getResponseCode(), headers
);

View File

@@ -13,7 +13,9 @@ import dan200.computercraft.api.lua.LuaException;
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;
@@ -26,6 +28,8 @@ 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";
@@ -174,8 +178,11 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
{
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( new TextWebSocketFrame( text ) );
channel.writeAndFlush( binary
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
: new TextWebSocketFrame( text ) );
return null;
}
case 2:

View File

@@ -8,13 +8,14 @@ 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.atomic.AtomicInteger;
import java.util.concurrent.ThreadFactory;
public class ComputerThread
{
@@ -56,8 +57,8 @@ public class ComputerThread
*/
private static Thread[] s_threads = null;
private static final AtomicInteger s_ManagerCounter = new AtomicInteger( 1 );
private static final AtomicInteger s_DelegateCounter = new AtomicInteger( 1 );
private static final ThreadFactory s_ManagerFactory = ThreadUtils.factory( "Computer-Manager" );
private static final ThreadFactory s_RunnerFactory = ThreadUtils.factory( "Computer-Runner" );
/**
* Start the computer thread
@@ -72,16 +73,12 @@ 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() )
{
thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
(s_threads[i] = s_ManagerFactory.newThread( new TaskExecutor() )).start();
}
}
}
@@ -188,12 +185,7 @@ public class ComputerThread
if( thread == null || !thread.isAlive() )
{
runner = new TaskRunner();
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();
(thread = s_RunnerFactory.newThread( runner )).start();
}
long start = System.nanoTime();

View File

@@ -11,6 +11,7 @@ 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;
@@ -114,6 +115,7 @@ 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 )
@@ -126,4 +128,19 @@ 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" );
}
}

View File

@@ -9,7 +9,9 @@ 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
@@ -45,8 +47,17 @@ public class EmptyMount implements IMount
@Nonnull
@Override
public InputStream openForRead( @Nonnull String path )
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
return null;
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" );
}
}

View File

@@ -6,61 +6,47 @@
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 int MINIMUM_FILE_SIZE = 500;
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 CountingOutputStream extends OutputStream
private class WritableCountingChannel implements WritableByteChannel
{
private OutputStream m_innerStream;
private long m_ignoredBytesLeft;
public CountingOutputStream( OutputStream innerStream, long bytesToIgnore )
private final WritableByteChannel m_inner;
long m_ignoredBytesLeft;
WritableCountingChannel( WritableByteChannel inner, long bytesToIgnore )
{
m_innerStream = innerStream;
m_inner = inner;
m_ignoredBytesLeft = bytesToIgnore;
}
@Override
public void close() throws IOException
public int write( @Nonnull ByteBuffer b ) throws IOException
{
m_innerStream.close();
count( b.remaining() );
return m_inner.write( b );
}
@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 void write( int b ) throws IOException
{
count( 1 );
m_innerStream.write( b );
}
private void count( long n ) throws IOException
void count( long n ) throws IOException
{
m_ignoredBytesLeft -= n;
if( m_ignoredBytesLeft < 0 )
@@ -79,6 +65,73 @@ 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;
@@ -183,6 +236,7 @@ public class FileMount implements IWritableMount
@Nonnull
@Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
if( created() )
@@ -193,7 +247,22 @@ 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
@@ -282,7 +351,23 @@ public class FileMount implements IWritableMount
@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 );
@@ -308,13 +393,13 @@ public class FileMount implements IWritableMount
m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE );
m_usedSpace += MINIMUM_FILE_SIZE;
}
return new CountingOutputStream( new FileOutputStream( file, false ), MINIMUM_FILE_SIZE );
return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE );
}
}
@Nonnull
@Override
public OutputStream openForAppend( @Nonnull String path ) throws IOException
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
if( created() )
{
@@ -329,7 +414,11 @@ public class FileMount implements IWritableMount
}
else
{
return new CountingOutputStream( new FileOutputStream( file, true ), Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) );
// 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 )
);
}
}
else

View File

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

View File

@@ -1,185 +1,132 @@
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
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;
public class FileSystemMount implements IFileSystem
public class FileSystemMount implements IMount
{
private final FileSystem m_filesystem;
private final Entry rootEntry;
public FileSystemMount( FileSystem m_filesystem )
public FileSystemMount( FileSystem fileSystem, String root ) throws IOException
{
this.m_filesystem = m_filesystem;
}
Path rootPath = fileSystem.getPath( root );
rootEntry = new Entry( "", rootPath );
@Override
public void makeDirectory( @Nonnull String path ) throws IOException
{
try
Queue<Entry> entries = new ArrayDeque<>();
entries.add( rootEntry );
while( !entries.isEmpty() )
{
m_filesystem.makeDir( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
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 );
}
}
}
}
@Override
public void delete( @Nonnull String path ) throws IOException
public boolean exists( @Nonnull String path )
{
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() );
}
return getFile( path ) != null;
}
@Override
public long getRemainingSpace() throws IOException
public boolean isDirectory( @Nonnull String path )
{
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() );
}
Entry entry = getFile( path );
return entry != null && entry.directory;
}
@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() );
}
Entry entry = getFile( path );
if( entry == null || !entry.directory ) throw new IOException( "/" + path + ": Not a directory" );
contents.addAll( entry.children.keySet() );
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.getSize( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
Entry file = getFile( path );
if( file == null ) throw new IOException( "/" + path + ": No such file" );
return file.size;
}
@Nonnull
@Override
@Deprecated
public InputStream openForRead( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForRead( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
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 String combine( String path, String child )
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return m_filesystem.combine( path, child );
Entry file = getFile( path );
if( file == null || file.directory ) throw new IOException( "/" + path + ": No such file" );
return Files.newByteChannel( file.path, StandardOpenOption.READ );
}
@Override
public void copy( String from, String to ) throws IOException
private Entry getFile( String path )
{
try
if( path.equals( "" ) ) return rootEntry;
if( !path.contains( "/" ) ) return rootEntry.children.get( path );
String[] components = path.split( "/" );
Entry entry = rootEntry;
for( String component : components )
{
m_filesystem.copy( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
if( entry == null || entry.children == null ) return null;
entry = entry.children.get( component );
}
return entry;
}
@Override
public void move( String from, String to ) throws IOException
private static class Entry
{
try
final String name;
final Path path;
final boolean directory;
final long size;
final Map<String, Entry> children;
private Entry( String name, Path path ) throws IOException
{
m_filesystem.move( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
if( name.endsWith( "/" ) || name.endsWith( "\\" ) ) name = name.substring( 0, name.length() - 1 );
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;
}
}
}

View File

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

View File

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

View File

@@ -20,9 +20,10 @@ import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@Deprecated
public class JarMount implements IMount
{
private class FileInZip
private static class FileInZip
{
private String m_path;
private boolean m_directory;
@@ -73,7 +74,7 @@ public class JarMount implements IMount
// 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 );
@@ -109,6 +110,7 @@ public class JarMount implements IMount
private FileInZip m_root;
private String m_rootPath;
@Deprecated
public JarMount( File jarFile, String subPath ) throws IOException
{
if( !jarFile.exists() || jarFile.isDirectory() )
@@ -203,7 +205,7 @@ public class JarMount implements IMount
}
else
{
throw new IOException( "/" + path + ": Not a directory" );
throw new IOException( "/" + path + ": Not a directory" );
}
}
@@ -215,7 +217,7 @@ public class JarMount implements IMount
{
return file.getSize();
}
throw new IOException( "/" + path + ": No such file" );
throw new IOException( "/" + path + ": No such file" );
}
@Nonnull
@@ -243,6 +245,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" );
}
}

View File

@@ -11,6 +11,7 @@ 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
@@ -52,11 +53,19 @@ 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 )

View File

@@ -7,14 +7,13 @@
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
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 dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.*;
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;
@@ -25,7 +24,7 @@ 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.AbstractResourceManipulator;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException;
@@ -35,6 +34,9 @@ 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;
@@ -42,12 +44,19 @@ 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 final LuaState m_state;
private final LuaTable m_globals;
private LuaState m_state;
private LuaTable m_globals;
private LuaThread m_mainRoutine;
private String m_eventFilter;
private String m_softAbortMessage;
private String m_hardAbortMessage;
@@ -57,60 +66,71 @@ public class CobaltLuaMachine implements ILuaMachine
m_computer = computer;
// Create an environment to run in
final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
{
@Override
public InputStream findResource( String filename )
LuaState state = this.m_state = LuaState.builder()
.resourceManipulator( new VoidResourceManipulator() )
.debug( new DebugHandler()
{
return null;
}
} );
state.debug = new DebugHandler( state )
{
private int count = 0;
private boolean hasSoftAbort;
private int count = 0;
private boolean hasSoftAbort;
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
{
int count = ++this.count;
if( count > 100000 )
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
this.count = 0;
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 );
}
else
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( m_state, NONE );
handleSoftAbort();
}
super.onInstruction( ds, di, pc, extras, top );
}
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;
}
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
handleSoftAbort();
}
if( hasSoftAbort && m_hardAbortMessage == null )
{
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
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;
hasSoftAbort = true;
throw new LuaError( message );
}
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);
}
};
} )
.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();
m_globals = new LuaTable();
state.setupThread( m_globals );
@@ -128,7 +148,7 @@ public class CobaltLuaMachine implements ILuaMachine
LibFunction.bind( state, 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 );
@@ -166,10 +186,7 @@ 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
{
@@ -178,30 +195,19 @@ public class CobaltLuaMachine implements ILuaMachine
}
catch( CompileException e )
{
if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
unload();
}
catch( IOException e )
{
ComputerCraft.log.warn( "Could not load bios.lua ", e );
if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
unload();
}
}
@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" ) )
{
@@ -228,26 +234,14 @@ public class CobaltLuaMachine implements ILuaMachine
else
{
LuaValue filter = results.arg( 2 );
if( filter.isString() )
{
m_eventFilter = filter.toString();
}
else
{
m_eventFilter = null;
}
m_eventFilter = filter.isString() ? filter.toString() : null;
}
LuaThread mainThread = m_mainRoutine;
if( mainThread.getStatus().equals( "dead" ) )
{
m_mainRoutine = null;
}
if( m_mainRoutine.getStatus().equals( "dead" ) ) unload();
}
catch( LuaError e )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
unload();
}
finally
{
@@ -284,18 +278,18 @@ public class CobaltLuaMachine implements ILuaMachine
@Override
public boolean isFinished()
{
return (m_mainRoutine == null);
return m_mainRoutine == null;
}
@Override
public void unload()
{
if( m_mainRoutine != null )
{
LuaThread mainThread = m_mainRoutine;
mainThread.abandon();
m_mainRoutine = null;
}
if( m_state == null ) return;
m_state.abandon();
m_mainRoutine = null;
m_state = null;
m_globals = null;
}
private LuaTable wrapLuaObject( ILuaObject object )
@@ -691,7 +685,7 @@ public class CobaltLuaMachine implements ILuaMachine
private byte[] bytes;
private int offset, remaining = 0;
public StringInputStream( LuaState state, LuaValue func )
StringInputStream( LuaState state, LuaValue func )
{
this.state = state;
this.func = func;

View File

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

View File

@@ -28,6 +28,9 @@ 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;

View File

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

View File

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

View File

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

View File

@@ -14,53 +14,43 @@ 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 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 IComputerAccess m_computer;
private final TIntSet m_channels;
private final Set<IComputerAccess> m_computers = new HashSet<>( 1 );
private final ModemState m_state;
private boolean m_open;
private boolean m_changed;
public ModemPeripheral()
protected ModemPeripheral( ModemState state )
{
m_network = null;
m_computer = null;
m_channels = new TIntHashSet();
m_open = false;
m_changed = true;
this.m_state = state;
}
public ModemState getModemState()
{
return m_state;
}
private synchronized void setNetwork( IPacketNetwork network )
{
if( m_network != network )
{
// Leave old network
if( m_network != null )
{
m_network.removeReceiver( this );
}
if( m_network == network ) return;
// Set new network
m_network = network;
// Leave old network
if( m_network != null ) m_network.removeReceiver( this );
// Join new network
if( m_network != null )
{
m_network.addReceiver( this );
}
}
// Set new network
m_network = network;
// Join new network
if( m_network != null ) m_network.addReceiver( this );
}
protected void switchNetwork()
@@ -68,39 +58,22 @@ public abstract class ModemPeripheral
setNetwork( getNetwork() );
}
public synchronized void destroy()
public 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 ) return;
if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return;
synchronized (m_channels)
synchronized( m_computers )
{
if( m_computer != null && m_channels.contains( packet.getChannel() ) )
for( IComputerAccess computer : m_computers )
{
m_computer.queueEvent( "modem_message", new Object[] {
m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
computer.queueEvent( "modem_message", new Object[]{
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
} );
}
}
@@ -109,14 +82,14 @@ public abstract class ModemPeripheral
@Override
public void receiveDifferentDimension( @Nonnull Packet packet )
{
if( packet.getSender() == this ) return;
if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return;
synchronized (m_channels)
synchronized( m_computers )
{
if( m_computer != null && m_channels.contains( packet.getChannel() ) )
for( IComputerAccess computer : m_computers )
{
m_computer.queueEvent( "modem_message", new Object[] {
m_computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
computer.queueEvent( "modem_message", new Object[]{
computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
} );
}
}
@@ -137,7 +110,7 @@ public abstract class ModemPeripheral
@Override
public String[] getMethodNames()
{
return new String[] {
return new String[]{
"open",
"isOpen",
"close",
@@ -166,68 +139,26 @@ public abstract class ModemPeripheral
{
// open
int channel = parseChannel( arguments, 0 );
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;
}
}
}
m_state.open( channel );
return null;
}
case 1:
{
// isOpen
int channel = parseChannel( arguments, 0 );
synchronized( this )
{
boolean open = m_channels.contains( channel );
return new Object[] { open };
}
return new Object[]{ m_state.isOpen( channel ) };
}
case 2:
{
// close
int channel = parseChannel( arguments, 0 );
synchronized( this )
{
if( m_channels.remove( channel ) )
{
if( m_channels.size() == 0 )
{
m_open = false;
m_changed = true;
}
}
}
m_state.close( channel );
return null;
}
case 3:
{
// closeAll
synchronized( this )
{
if( m_channels.size() > 0 )
{
m_channels.clear();
if( m_open )
{
m_open = false;
m_changed = true;
}
}
}
m_state.closeAll();
return null;
}
case 4:
@@ -235,12 +166,12 @@ public abstract class ModemPeripheral
// transmit
int channel = parseChannel( arguments, 0 );
int replyChannel = parseChannel( arguments, 1 );
Object payload = (arguments.length >= 3) ? arguments[2] : null;
Object payload = arguments.length > 2 ? 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() )
@@ -258,14 +189,8 @@ public abstract class ModemPeripheral
case 5:
{
// isWireless
synchronized( this )
{
if( m_network != null )
{
return new Object[] { m_network.isWireless() };
}
}
return new Object[] { false };
IPacketNetwork network = m_network;
return new Object[]{ network != null && network.isWireless() };
}
default:
{
@@ -277,46 +202,42 @@ public abstract class ModemPeripheral
@Override
public synchronized void attach( @Nonnull IComputerAccess computer )
{
m_computer = computer;
synchronized( m_computers )
{
m_computers.add( computer );
}
setNetwork( getNetwork() );
m_open = !m_channels.isEmpty();
}
@Override
public synchronized void detach( @Nonnull IComputerAccess computer )
{
if( m_network != null )
boolean empty;
synchronized( m_computers )
{
m_network.removeReceiver( this );
m_channels.clear();
m_network = null;
m_computers.remove( computer );
empty = m_computers.isEmpty();
}
m_computer = null;
if( m_open )
{
m_open = false;
m_changed = true;
}
}
public IComputerAccess getComputer()
{
return m_computer;
if( empty ) setNetwork( null );
}
@Nonnull
@Override
public String getSenderID()
{
if( m_computer == null )
synchronized( m_computers )
{
return "unknown";
}
else
{
return m_computer.getID() + "_" + m_computer.getAttachmentName();
if( m_computers.size() != 1 )
{
return "unknown";
}
else
{
IComputerAccess computer = m_computers.iterator().next();
return computer.getID() + "_" + computer.getAttachmentName();
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -56,10 +56,7 @@ 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 );
@@ -79,7 +76,7 @@ public abstract class TileModemBase extends TilePeripheralBase
public void update()
{
super.update();
if( !getWorld().isRemote && m_modem.pollChanged() )
if( !getWorld().isRemote && m_modem.getModemState().pollChanged() )
{
updateAnim();
}
@@ -87,14 +84,7 @@ public abstract class TileModemBase extends TilePeripheralBase
protected void updateAnim()
{
if( m_modem.isActive() )
{
setAnim(1);
}
else
{
setAnim(0);
}
setAnim( m_modem.getModemState().isOpen() ? 1 : 0 );
}
@Override
@@ -109,15 +99,6 @@ public abstract class TileModemBase extends TilePeripheralBase
@Override
public IPeripheral getPeripheral( EnumFacing side )
{
if( side == getDirection() )
{
return m_modem;
}
return null;
}
protected boolean isAttached()
{
return (m_modem != null) && (m_modem.getComputer() != null);
return side == getDirection() ? m_modem : null;
}
}

View File

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

View File

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

View File

@@ -18,6 +18,8 @@ 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;
@@ -25,10 +27,11 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
{
private final WiredModemElement modem;
private final Map<String, RemotePeripheralWrapper> peripheralWrappers = new HashMap<>();
private final Map<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> peripheralWrappers = new HashMap<>( 1 );
public WiredModemPeripheral( WiredModemElement modem )
public WiredModemPeripheral( ModemState state, WiredModemElement modem )
{
super( state );
this.modem = modem;
}
@@ -88,56 +91,54 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
case 0:
{
// getNamesRemote
synchronized( peripheralWrappers )
Map<String, RemotePeripheralWrapper> wrappers = getWrappers( computer );
Map<Object, Object> table = new HashMap<>();
if( wrappers != null )
{
int idx = 1;
Map<Object, Object> table = new HashMap<>();
for( String name : peripheralWrappers.keySet() )
{
table.put( idx++, name );
}
return new Object[]{ table };
for( String name : wrappers.keySet() ) table.put( idx++, name );
}
return new Object[]{ table };
}
case 1:
{
// isPresentRemote
String type = getTypeRemote( getString( arguments, 0 ) );
return new Object[]{ type != null };
String name = getString( arguments, 0 );
return new Object[]{ getWrapper( computer, name ) != null };
}
case 2:
{
// getTypeRemote
String type = getTypeRemote( getString( arguments, 0 ) );
if( type != null )
{
return new Object[]{ type };
}
return null;
String name = getString( arguments, 0 );
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
return wrapper != null ? new Object[]{ wrapper.getType() } : null;
}
case 3:
{
// getMethodsRemote
String[] methodNames = getMethodNamesRemote( getString( arguments, 0 ) );
if( methodNames != null )
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 )
{
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < methodNames.length; ++i )
{
table.put( i + 1, methodNames[i] );
}
return new Object[]{ table };
table.put( i + 1, methodNames[i] );
}
return null;
return new Object[]{ table };
}
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 callMethodRemote( remoteName, context, methodName, methodArgs );
return wrapper.callMethod( context, methodName, methodArgs );
}
case 5:
{
@@ -157,29 +158,37 @@ 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() )
{
synchronized( peripheralWrappers )
for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() )
{
for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() )
{
attachPeripheralImpl( entry.getKey(), entry.getValue() );
}
attachPeripheralImpl( computer, wrappers, entry.getKey(), entry.getValue() );
}
}
}
@Override
public synchronized void detach( @Nonnull IComputerAccess computer )
public void detach( @Nonnull IComputerAccess computer )
{
Map<String, RemotePeripheralWrapper> wrappers;
synchronized( peripheralWrappers )
{
for( RemotePeripheralWrapper wrapper : peripheralWrappers.values() )
{
wrapper.detach();
}
peripheralWrappers.clear();
wrappers = peripheralWrappers.remove( computer );
}
if( wrappers != null )
{
for( RemotePeripheralWrapper wrapper : wrappers.values() ) wrapper.detach();
wrappers.clear();
}
super.detach( computer );
}
@@ -204,11 +213,12 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
public void attachPeripheral( String name, IPeripheral peripheral )
{
if( getComputer() == null ) return;
synchronized( peripheralWrappers )
{
attachPeripheralImpl( name, peripheral );
for( Map.Entry<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> entry : peripheralWrappers.entrySet() )
{
attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral );
}
}
}
@@ -216,63 +226,35 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
{
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( name );
if( wrapper != null )
{
peripheralWrappers.remove( name );
wrapper.detach();
for(ConcurrentMap<String, RemotePeripheralWrapper> wrappers : peripheralWrappers.values()) {
RemotePeripheralWrapper wrapper = wrappers.remove( name );
if( wrapper != null ) wrapper.detach();
}
}
}
private void attachPeripheralImpl( String periphName, IPeripheral peripheral )
private void attachPeripheralImpl( IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral )
{
if( !peripheralWrappers.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) )
if( !peripherals.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) )
{
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
peripheralWrappers.put( periphName, wrapper );
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, computer, periphName );
peripherals.put( periphName, wrapper );
wrapper.attach();
}
}
private String getTypeRemote( String remoteName )
{
private ConcurrentMap<String, RemotePeripheralWrapper> getWrappers( IComputerAccess computer ) {
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getType();
}
return peripheralWrappers.get( computer );
}
return null;
}
private String[] getMethodNamesRemote( String remoteName )
private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String remoteName )
{
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getMethodNames();
}
}
return null;
}
private Object[] callMethodRemote( String remoteName, ILuaContext context, String method, Object[] arguments ) throws LuaException, InterruptedException
{
RemotePeripheralWrapper wrapper;
synchronized( peripheralWrappers )
{
wrapper = peripheralWrappers.get( remoteName );
}
if( wrapper != null )
{
return wrapper.callMethod( context, method, arguments );
}
throw new LuaException( "No peripheral: " + remoteName );
ConcurrentMap<String, RemotePeripheralWrapper> wrappers = getWrappers( computer );
return wrappers == null ? null : wrappers.get( remoteName );
}
private static class RemotePeripheralWrapper implements IComputerAccess, IComputerOwned

View File

@@ -15,9 +15,16 @@ 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 )
{
m_advanced = advanced;
this( new ModemState(), advanced );
}
@Override

View File

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

View File

@@ -66,7 +66,8 @@ public class MonitorPeripheral implements IPeripheral
"setPaletteColor",
"getPaletteColour",
"getPaletteColor",
"getTextScale"
"getTextScale",
"getCursorBlink",
};
}
@@ -245,8 +246,12 @@ public class MonitorPeripheral implements IPeripheral
// getTextScale
return new Object[] { monitor.getTextScale() / 2.0 };
}
case 25:
// getCursorBlink
return new Object[] { terminal.getCursorBlink() };
default:
return null;
}
return null;
}
@Override

View File

@@ -8,10 +8,11 @@ package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
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;
@@ -25,22 +26,23 @@ 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 TileSpeaker m_speaker;
public class SpeakerPeripheral implements IPeripheral
{
private final 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;
}
@@ -70,15 +72,9 @@ public class SpeakerPeripheral implements IPeripheral {
@Override
public boolean equals( IPeripheral other )
{
if( other != null && other instanceof SpeakerPeripheral )
{
SpeakerPeripheral otherSpeaker = (SpeakerPeripheral) other;
return otherSpeaker.m_speaker == m_speaker;
}
else
{
return false;
}
if( other == this ) return true;
if( !(other instanceof SpeakerPeripheral) ) return false;
return m_speaker == ((SpeakerPeripheral) other).m_speaker;
}
@Nonnull
@@ -93,8 +89,8 @@ 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
};
}
@@ -106,18 +102,22 @@ public class SpeakerPeripheral implements IPeripheral {
// playSound
case 0:
{
return playSound(args, context, false);
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 ) };
}
// playNote
case 1:
{
return playNote(args, context);
return playNote( args, context );
}
default:
{
throw new LuaException("Method index out of range!");
throw new LuaException( "Method index out of range!" );
}
}
@@ -126,75 +126,53 @@ public class SpeakerPeripheral implements IPeripheral {
@Nonnull
private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException
{
String name = getString(arguments, 0);
String name = getString( arguments, 0 );
float volume = (float) optReal( arguments, 1, 1.0 );
float pitch = (float) optReal( arguments, 2, 1.0 );
// Check if sound exists
if ( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( "block.note." + name ) ) )
String noteName = "block.note." + name;
// Check if the note exists
if( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( noteName ) ) )
{
throw new LuaException("Invalid instrument, \"" + arguments[0] + "\"!");
throw new LuaException( "Invalid instrument, \"" + name + "\"!" );
}
// If the resource location for note block notes changes, this method call will need to be updated
Object[] returnValue = playSound(
new Object[] {
"block.note." + name,
(double)Math.min( volume, 3f ),
Math.pow( 2.0f, ( pitch - 12.0f ) / 12.0f)
}, context, true
);
boolean success = playSound( context, noteName, volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true );
if( returnValue[0] instanceof Boolean && (Boolean) returnValue[0] )
{
m_notesThisTick.incrementAndGet();
}
return returnValue;
if( success ) m_notesThisTick.incrementAndGet();
return new Object[] { success };
}
@Nonnull
private synchronized Object[] playSound( Object[] arguments, ILuaContext context, boolean isNote ) throws LuaException
private synchronized boolean playSound( ILuaContext context, String name, float volume, float pitch, 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 );
ResourceLocation resourceName = new ResourceLocation( name );
if( m_clock - m_lastPlayTime >= TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS || (isNote && m_clock - m_lastPlayTime == 0 && m_notesThisTick.get() < ComputerCraft.maxNotesPerTick) )
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 new Object[]{true}; // Success, return true
}
else
{
return new Object[]{false}; // Failed - sound not existent, return false
}
}
else
{
return new Object[]{false}; // Failed - rate limited, return false
// 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;
}
World world = getWorld();
BlockPos pos = getPos();
context.issueMainThreadTask( () -> {
MinecraftServer server = world.getMinecraftServer();
if( server == null ) return null;
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;
} );
m_lastPlayTime = m_clock;
return true;
}
}

View File

@@ -5,6 +5,7 @@ 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;
@@ -75,7 +76,8 @@ public class PocketModem implements IPocketUpgrade
modem.setLocation( entity.getEntityWorld(), entity.posX, entity.posY, entity.posZ );
}
access.setLight( modem.isActive() ? 0xBA0000 : -1 );
ModemState state = modem.getModemState();
if( state.pollChanged() ) access.setLight( state.isOpen() ? 0xBA0000 : -1 );
}
}
}

View File

@@ -6,6 +6,7 @@
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;
@@ -19,7 +20,7 @@ public class PocketModemPeripheral extends WirelessModemPeripheral
public PocketModemPeripheral( boolean advanced )
{
super( advanced );
super( new ModemState(), advanced );
m_world = null;
m_position = new Vec3d( 0.0, 0.0, 0.0 );
}

View File

@@ -469,9 +469,9 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
private void registerTileEntities()
{
// TileEntities
GameRegistry.registerTileEntity( TileTurtle.class, ComputerCraft.LOWER_ID + " : " + "turtle" );
GameRegistry.registerTileEntity( TileTurtleExpanded.class, ComputerCraft.LOWER_ID + " : " + "turtleex" );
GameRegistry.registerTileEntity( TileTurtleAdvanced.class, ComputerCraft.LOWER_ID + " : " + "turtleadv" );
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" ) );
}
private void registerForgeHandlers()

View File

@@ -24,6 +24,7 @@ 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;
@@ -77,6 +78,7 @@ 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;
@@ -121,6 +123,7 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
registerTileEntities();
registerForgeHandlers();
Fixes.register( FMLCommonHandler.instance().getDataFixer() );
if( Loader.isModLoaded( ModCharset.MODID ) ) IntegrationCharset.register();
}
@@ -478,16 +481,16 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
private void registerTileEntities()
{
// Tile Entities
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" );
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" ) );
// Register peripheral providers
ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() );

View File

@@ -10,6 +10,7 @@ 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;
@@ -17,9 +18,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;
@@ -37,7 +38,7 @@ public class TurtleModem implements ITurtleUpgrade
public Peripheral( ITurtleAccess turtle, boolean advanced )
{
super( advanced );
super( new ModemState(), advanced );
m_turtle = turtle;
}
@@ -221,12 +222,12 @@ public class TurtleModem implements ITurtleUpgrade
if( !turtle.getWorld().isRemote )
{
IPeripheral peripheral = turtle.getPeripheral( side );
if( peripheral != null && peripheral instanceof Peripheral )
if( peripheral instanceof Peripheral )
{
Peripheral modemPeripheral = (Peripheral)peripheral;
if( modemPeripheral.pollChanged() )
ModemState state = ((Peripheral) peripheral).getModemState();
if( state.pollChanged() )
{
turtle.getUpgradeNBTData( side ).setBoolean( "active", modemPeripheral.isActive() );
turtle.getUpgradeNBTData( side ).setBoolean( "active", state.isOpen() );
turtle.updateUpgradeNBTData( side );
}
}

View File

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

View File

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

View File

@@ -1,202 +1,249 @@
-- Definition for the IO API
local typeOf = _G.type
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,
}
--- 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 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, ... ) )
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,
flush = function( self )
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_currentInput = g_defaultInput
local g_currentOutput = g_defaultOutput
local defaultInput = setmetatable({
_handle = { readLine = _G.read }
}, handleMetatable)
function close( _file )
(_file or g_currentOutput):close()
local defaultOutput = setmetatable({
_handle = { write = _G.write }
}, handleMetatable)
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()
end
function flush()
g_currentOutput:flush()
return currentOutput:flush()
end
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 )
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)
end
if _sFileName then
return open( _sFileName, "r" ):lines()
else
return g_currentInput:lines()
end
return currentInput
end
function open( _sPath, _sMode )
if _G.type( _sPath ) ~= "string" then
error( "bad argument #1 (expected string, got " .. _G.type( _sPath ) .. ")", 2 )
function lines(_sFileName)
if _sFileName ~= nil and typeOf(_sFileName) ~= "string" then
error("bad argument #1 (expected string, got " .. typeOf(_sFileName) .. ")", 2)
end
if _sMode ~= nil and _G.type( _sMode ) ~= "string" then
error( "bad argument #2 (expected string, got " .. _G.type( _sMode ) .. ")", 2 )
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()
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 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
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)
end
function read( ... )
return input():read( ... )
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
end
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
function read(...)
return currentInput:read(...)
end
function write( ... )
return output():write( ... )
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(...)
end

View File

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

View File

@@ -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 then
if type( tMessage ) == "table" and tMessage.nMessageID and tMessage.nRecipient and type(tMessage.nRecipient) == "number" then
if not tReceivedMessages[ tMessage.nMessageID ] then
-- Ensure we only repeat a message once
tReceivedMessages[ tMessage.nMessageID ] = true

View File

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

View File

@@ -0,0 +1,90 @@
/*
* 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" );
}
}