From 49601f0b7c78df9299be2819b813e44bc7c654d9 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 29 Apr 2022 22:35:41 +0100 Subject: [PATCH 01/22] Bump Cobalt version Oh deary me. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ace5b9938..8c90c9e27 100644 --- a/build.gradle +++ b/build.gradle @@ -146,7 +146,7 @@ dependencies { runtimeOnly fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104") - shade 'org.squiddev:Cobalt:0.5.2-SNAPSHOT' + shade 'org.squiddev:Cobalt:0.5.5' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' From 6239dbe9caea448764c47159f35215a733a704b0 Mon Sep 17 00:00:00 2001 From: JackMacWindows Date: Sun, 1 May 2022 03:29:43 -0400 Subject: [PATCH 02/22] Add documentation on full list of 5.2/5.3 features (#1071) --- doc/events/mouse_click.md | 12 +-- doc/guides/feature_compat.md | 95 +++++++++++++++++++ .../computercraft/lua/rom/apis/colors.lua | 2 +- src/web/styles.css | 6 +- 4 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 doc/guides/feature_compat.md diff --git a/doc/events/mouse_click.md b/doc/events/mouse_click.md index ed4f2e3eb..1dc1196e2 100644 --- a/doc/events/mouse_click.md +++ b/doc/events/mouse_click.md @@ -15,13 +15,11 @@ advanced turtles and pocket computers). Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a numerical value depending on which button on your mouse was last pressed when this event occurred. - - - - - - -
Button codeMouse button
1Left button
2Right button
3Middle button
+| Button Code | Mouse Button | +|------------:|---------------| +| 1 | Left button | +| 2 | Right button | +| 3 | Middle button | ## Example Print the button and the coordinates whenever the mouse is clicked. diff --git a/doc/guides/feature_compat.md b/doc/guides/feature_compat.md new file mode 100644 index 000000000..a72f5046f --- /dev/null +++ b/doc/guides/feature_compat.md @@ -0,0 +1,95 @@ +--- +module: [kind=guide] feature_compat +--- + +# Lua 5.2/5.3 features in CC: Tweaked +CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions. + +## Lua 5.2 +| Feature | Supported? | Notes | +|---------------------------------------------------------------|------------|-------------------------------------------------------------------| +| `goto`/labels | ❌ | | +| `_ENV` | πŸ”Ά | The `_ENV` global points to `getfenv()`, but it cannot be set. | +| `\z` escape | βœ” | | +| `\xNN` escape | βœ” | | +| Hex literal fractional/exponent parts | βœ” | | +| Empty statements | ❌ | | +| `__len` metamethod | βœ” | | +| `__ipairs` metamethod | ❌ | | +| `__pairs` metamethod | βœ” | | +| `bit32` library | βœ” | | +| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. | +| New `load` syntax | βœ” | | +| `loadfile` mode parameter | βœ” | Supports both 5.1 and 5.2+ syntax. | +| Removed `loadstring` | πŸ”Ά | Only if `disable_lua51_features` is enabled in the configuration. | +| Removed `getfenv`, `setfenv` | πŸ”Ά | Only if `disable_lua51_features` is enabled in the configuration. | +| `rawlen` function | βœ” | | +| Negative index to `select` | βœ” | | +| Removed `unpack` | πŸ”Ά | Only if `disable_lua51_features` is enabled in the configuration. | +| Arguments to `xpcall` | ❌ | | +| Second return value from `coroutine.running` | ❌ | | +| Removed `module` | βœ” | | +| `package.loaders` -> `package.searchers` | ❌ | | +| Second argument to loader functions | βœ” | | +| `package.config` | βœ” | | +| `package.searchpath` | βœ” | | +| Removed `package.seeall` | βœ” | | +| `string.dump` on functions with upvalues (blanks them out) | βœ” | | +| `string.rep` separator | ❌ | | +| `%g` match group | ❌ | | +| Removal of `%z` match group | ❌ | | +| Removed `table.maxn` | πŸ”Ά | Only if `disable_lua51_features` is enabled in the configuration. | +| `table.pack`/`table.unpack` | βœ” | | +| `math.log` base argument | βœ” | | +| Removed `math.log10` | πŸ”Ά | Only if `disable_lua51_features` is enabled in the configuration. | +| `*L` mode to `file:read` | βœ” | | +| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. | +| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. | +| `istailcall` field in `debug.getinfo` | ❌ | | +| `nparams` field in `debug.getinfo` | βœ” | | +| `isvararg` field in `debug.getinfo` | βœ” | | +| `debug.getlocal` negative indices for varargs | ❌ | | +| `debug.getuservalue`/`debug.setuservalue` | ❌ | Userdata are rarely used in CC:T, so this is not necessary. | +| `debug.upvalueid` | βœ” | | +| `debug.upvaluejoin` | βœ” | | +| Tail call hooks | ❌ | | +| `=` prefix for chunks | βœ” | | +| Yield across C boundary | βœ” | | +| Removal of ambiguity error | ❌ | | +| Identifiers may no longer use locale-dependent letters | βœ” | | +| Ephemeron tables | ❌ | | +| Identical functions may be reused | ❌ | | +| Generational garbage collector | ❌ | Cobalt uses the built-in Java garbage collector. | + +## Lua 5.3 +| Feature | Supported? | Notes | +|---------------------------------------------------------------------------------------|------------|---------------------------| +| Integer subtype | ❌ | | +| Bitwise operators/floor division | ❌ | | +| `\u{XXX}` escape sequence | βœ” | | +| `utf8` library | βœ” | | +| removed `__ipairs` metamethod | βœ” | | +| `coroutine.isyieldable` | ❌ | | +| `string.dump` strip argument | βœ” | | +| `string.pack`/`string.unpack`/`string.packsize` | βœ” | | +| `table.move` | ❌ | | +| `math.atan2` -> `math.atan` | ❌ | | +| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | | +| `math.maxinteger`/`math.mininteger` | ❌ | | +| `math.tointeger` | ❌ | | +| `math.type` | ❌ | | +| `math.ult` | ❌ | | +| Removed `bit32` library | ❌ | | +| Remove `*` from `file:read` modes | βœ” | | +| Metamethods respected in `table.*`, `ipairs` | πŸ”Ά | Only `__lt` is respected. | + +## Lua 5.0 +| Feature | Supported? | Notes | +|----------------------------------|------------|--------------------------------------------------| +| `arg` table | πŸ”Ά | Only set in the shell - not used in functions. | +| `string.gfind` | βœ” | Equal to `string.gmatch`. | +| `table.getn` | βœ” | Equal to `#tbl`. | +| `table.setn` | ❌ | | +| `math.mod` | βœ” | Equal to `math.fmod`. | +| `table.foreach`/`table.foreachi` | βœ” | | +| `gcinfo` | ❌ | Cobalt uses the built-in Java garbage collector. | diff --git a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua index a0e7e1629..2c9eccc62 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua @@ -16,7 +16,7 @@ terminal supports color by using the function @{term.isColor}. Grayscale colors are calculated by taking the average of the three components, i.e. `(red + green + blue) / 3`. - +
diff --git a/src/web/styles.css b/src/web/styles.css index 3192f0477..ad51a54d2 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -7,17 +7,17 @@ } /* Pretty tables, mostly inherited from table.definition-list */ -table.pretty-table { +table { border-collapse: collapse; width: 100%; } -table.pretty-table td, table.pretty-table th { +table td, table th { border: 1px solid #cccccc; padding: 2px 4px; } -table.pretty-table th { +table th { background-color: var(--background-2); } From e909e11e05cb0d6f7bce1e4500348b0ece7a2c1e Mon Sep 17 00:00:00 2001 From: Sr_endi <67484093+Seniorendi@users.noreply.github.com> Date: Sun, 1 May 2022 13:09:38 +0200 Subject: [PATCH 03/22] [1.16] Make blocks rotatable for structures (#1083) --- gradle.properties | 2 +- .../shared/computer/blocks/BlockComputer.java | 16 ++++++++++++++++ .../peripheral/diskdrive/BlockDiskDrive.java | 16 ++++++++++++++++ .../modem/wireless/BlockWirelessModem.java | 16 ++++++++++++++++ .../shared/peripheral/monitor/BlockMonitor.java | 16 ++++++++++++++++ .../shared/peripheral/printer/BlockPrinter.java | 16 ++++++++++++++++ .../shared/peripheral/speaker/BlockSpeaker.java | 17 +++++++++++++++++ .../shared/turtle/blocks/BlockTurtle.java | 16 ++++++++++++++++ src/main/resources/META-INF/mods.toml | 2 +- 9 files changed, 115 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8ee3b531d..11f25ee2a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ mod_version=1.100.5 # Minecraft properties (update mods.toml when changing) mc_version=1.16.5 mapping_version=2021.08.08 -forge_version=36.2.20 +forge_version=36.2.34 # NO SERIOUSLY, UPDATE mods.toml WHEN CHANGING diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java index f26f15c78..0a6b73cc9 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java @@ -18,6 +18,8 @@ import net.minecraft.state.StateContainer; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraftforge.fml.RegistryObject; import javax.annotation.Nonnull; @@ -50,6 +52,20 @@ public class BlockComputer extends BlockComputerBase return defaultBlockState().setValue( FACING, placement.getHorizontalDirection().getOpposite() ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nonnull @Override protected ItemStack getItem( TileComputerBase tile ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java index 42f1b57f6..ff9a3ac8a 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java @@ -21,6 +21,8 @@ import net.minecraft.stats.Stats; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.INameable; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -47,6 +49,20 @@ public class BlockDiskDrive extends BlockGeneric properties.add( FACING, STATE ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nullable @Override public BlockState getStateForPlacement( BlockItemUseContext placement ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java index 599bd2791..94889afb7 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java @@ -18,6 +18,8 @@ import net.minecraft.state.StateContainer; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; @@ -94,4 +96,18 @@ public class BlockWirelessModem extends BlockGeneric implements IWaterLoggable .setValue( FACING, placement.getClickedFace().getOpposite() ) .setValue( WATERLOGGED, getWaterloggedStateForPlacement( placement ) ); } + + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java index 36690e94a..a86d23128 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -18,6 +18,8 @@ import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; @@ -51,6 +53,20 @@ public class BlockMonitor extends BlockGeneric builder.add( ORIENTATION, FACING, STATE ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Override @Nullable public BlockState getStateForPlacement( BlockItemUseContext context ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java index a47a8cb77..b86a35532 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java @@ -21,6 +21,8 @@ import net.minecraft.stats.Stats; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.INameable; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -48,6 +50,20 @@ public class BlockPrinter extends BlockGeneric properties.add( FACING, TOP, BOTTOM ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nullable @Override public BlockState getStateForPlacement( BlockItemUseContext placement ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java index b695ecbd6..cd0350095 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java @@ -14,7 +14,10 @@ import net.minecraft.state.DirectionProperty; import net.minecraft.state.StateContainer; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; +import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BlockSpeaker extends BlockGeneric @@ -34,6 +37,20 @@ public class BlockSpeaker extends BlockGeneric properties.add( FACING ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nullable @Override public BlockState getStateForPlacement( BlockItemUseContext placement ) diff --git a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java index fd2671106..c0c48aede 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java @@ -29,7 +29,9 @@ import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; @@ -71,6 +73,20 @@ public class BlockTurtle extends BlockComputerBase implements IWater builder.add( FACING, WATERLOGGED ); } + @Nonnull + @Override + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nonnull @Override @Deprecated diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 55032ba56..438eb9b1e 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -20,6 +20,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a [[dependencies.computercraft]] modId="forge" mandatory=true - versionRange="[36.2.20,37)" + versionRange="[36.2.34,37)" ordering="NONE" side="BOTH" From 9cb7091ce7ee47624339ace9d992268eb1f83fcd Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 2 May 2022 16:21:56 +0100 Subject: [PATCH 04/22] Fix several deprecated warnings --- .../computercraft/shared/computer/blocks/BlockComputer.java | 2 ++ .../shared/peripheral/diskdrive/BlockDiskDrive.java | 2 ++ .../shared/peripheral/modem/wireless/BlockWirelessModem.java | 2 ++ .../computercraft/shared/peripheral/monitor/BlockMonitor.java | 2 ++ .../computercraft/shared/peripheral/printer/BlockPrinter.java | 2 ++ .../computercraft/shared/peripheral/speaker/BlockSpeaker.java | 2 ++ .../computercraft/shared/turtle/FurnaceRefuelHandler.java | 2 +- .../dan200/computercraft/shared/turtle/blocks/BlockTurtle.java | 2 ++ 8 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java index 0a6b73cc9..61bdc76aa 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java @@ -54,6 +54,7 @@ public class BlockComputer extends BlockComputerBase @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -61,6 +62,7 @@ public class BlockComputer extends BlockComputerBase @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java index ff9a3ac8a..e426bdcad 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java @@ -51,6 +51,7 @@ public class BlockDiskDrive extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -58,6 +59,7 @@ public class BlockDiskDrive extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java index 94889afb7..6b87f7c3a 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java @@ -99,6 +99,7 @@ public class BlockWirelessModem extends BlockGeneric implements IWaterLoggable @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -106,6 +107,7 @@ public class BlockWirelessModem extends BlockGeneric implements IWaterLoggable @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java index a86d23128..ae77ad488 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -55,6 +55,7 @@ public class BlockMonitor extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -62,6 +63,7 @@ public class BlockMonitor extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java index b86a35532..a03611816 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java @@ -52,6 +52,7 @@ public class BlockPrinter extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -59,6 +60,7 @@ public class BlockPrinter extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java index cd0350095..0867b3a8b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java @@ -39,6 +39,7 @@ public class BlockSpeaker extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -46,6 +47,7 @@ public class BlockSpeaker extends BlockGeneric @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java b/src/main/java/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java index 9c2fa1ece..018e95067 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java +++ b/src/main/java/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java @@ -52,7 +52,7 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler private static int getFuelPerItem( @Nonnull ItemStack stack ) { - return (ForgeHooks.getBurnTime( stack ) * 5) / 100; + return (ForgeHooks.getBurnTime( stack, null ) * 5) / 100; } @SubscribeEvent diff --git a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java index c0c48aede..1c5e82ca1 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java @@ -75,6 +75,7 @@ public class BlockTurtle extends BlockComputerBase implements IWater @Nonnull @Override + @Deprecated public BlockState mirror( BlockState state, Mirror mirrorIn ) { return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); @@ -82,6 +83,7 @@ public class BlockTurtle extends BlockComputerBase implements IWater @Nonnull @Override + @Deprecated public BlockState rotate( BlockState state, Rotation rot ) { return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); From cbbab26bf33b4c194bd56c9124a4814f684c4d35 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 2 May 2022 17:49:32 +0100 Subject: [PATCH 05/22] Some minor documentation improvements - Start making the summary lines for modules a little better. Just say what the module does, rather than "The X API does Y" or "Provides Y". There's still a lot of work to be done here. - Bundle prism.js on the page, so we can highlight non-Lua code. - Copy our local_ips wiki page to the main docs. --- doc/guides/local_ips.md | 95 +++++++++++++++++++ doc/stub/fs.lua | 4 +- doc/stub/global.lua | 11 ++- doc/stub/http.lua | 24 ++--- .../dan200/computercraft/core/apis/FSAPI.java | 4 +- .../computercraft/core/apis/HTTPAPI.java | 2 +- .../computercraft/core/apis/RedstoneAPI.java | 2 +- .../computercraft/core/apis/TermAPI.java | 3 +- .../computercraft/lua/rom/apis/colors.lua | 15 +-- .../computercraft/lua/rom/apis/colours.lua | 2 +- .../lua/rom/apis/command/commands.lua | 39 ++++---- .../data/computercraft/lua/rom/apis/disk.lua | 2 +- .../data/computercraft/lua/rom/apis/gps.lua | 4 +- .../data/computercraft/lua/rom/apis/help.lua | 2 +- .../data/computercraft/lua/rom/apis/keys.lua | 3 +- .../computercraft/lua/rom/apis/paintutils.lua | 4 +- .../computercraft/lua/rom/apis/parallel.lua | 27 +++++- .../computercraft/lua/rom/apis/peripheral.lua | 4 +- .../computercraft/lua/rom/apis/rednet.lua | 6 +- .../computercraft/lua/rom/apis/settings.lua | 3 +- .../data/computercraft/lua/rom/apis/term.lua | 5 +- .../computercraft/lua/rom/apis/textutils.lua | 3 +- .../computercraft/lua/rom/apis/vector.lua | 4 +- .../computercraft/lua/rom/apis/window.lua | 59 ++++++------ .../lua/rom/modules/main/cc/audio/dfpwm.lua | 2 +- .../lua/rom/modules/main/cc/image/nft.lua | 4 +- .../lua/rom/modules/main/cc/pretty.lua | 4 +- .../lua/rom/modules/main/cc/require.lua | 4 +- src/web/index.tsx | 6 +- src/web/prism.js | 5 + src/web/styles.css | 2 +- 31 files changed, 242 insertions(+), 112 deletions(-) create mode 100644 doc/guides/local_ips.md create mode 100644 src/web/prism.js diff --git a/doc/guides/local_ips.md b/doc/guides/local_ips.md new file mode 100644 index 000000000..c8adf74ce --- /dev/null +++ b/doc/guides/local_ips.md @@ -0,0 +1,95 @@ +--- +module: [kind=guide] local_ips +--- + +# Allowing access to local IPS +By default, ComputerCraft blocks access to local IP addresses for security. This means you can't normally access any +HTTP server running on your computer. However, this may be useful for testing programs without having a remote +server. You can unblock these IPs in the ComputerCraft config. + +## Minecraft 1.13 and later, CC:T 1.87.0 and later {#cc-1.87.0} +The configuration file can be located at `serverconfig/computercraft-server.toml` inside the world folder on either +single-player or multiplayer. Look for lines that look like this: + +```toml +#A list of rules which control behaviour of the "http" API for specific domains or IPs. +#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"), +#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked. +[[http.rules]] + host = "$private" + action = "deny" +``` + +On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of +`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have +`action = "deny"`. Then save the file and relaunch Minecraft (Server). + +Here's what it should look like after removing: + +```toml +#A list of rules which control behaviour of the "http" API for specific domains or IPs. +#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"), +#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked. +[[http.rules]] + #The maximum size (in bytes) that a computer can send or receive in one websocket packet. + max_websocket_message = 131072 + host = "*" + #The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text. + max_upload = 4194304 + action = "allow" + #The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client. + max_download = 16777216 + #The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited. + timeout = 30000 +``` + +## Minecraft 1.13 and later, CC:T 1.86.2 and earlier {#cc-1.86.2} +The configuration file for singleplayer is at `.minecraft/config/computercraft-common.toml`. Look for lines that look +like this: + +```toml +#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"] +``` + +Remove everything inside the array, leaving the last line as `blacklist = []`. Then save the file and relaunch Minecraft. + +Here's what it should look like after removing: + +```toml +#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +blacklist = [] +``` + +## Minecraft 1.12.2 and earlier {#mc-1.12} +On singleplayer, the configuration file is located at `.minecraft\config\ComputerCraft.cfg`. On multiplayer, the +configuration file is located at `\config\ComputerCraft.cfg`. Look for lines that look like this: + +```ini +# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +S:blocked_domains < + 127.0.0.0/8 + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + fd00::/8 + > +``` + +Delete everything between the `<>`, leaving the last line as `S:blocked_domains = <>`. Then save the file and relaunch +Minecraft (Server). + +Here's what it should look like after removing: + +```ini +# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +S:blocked_domains <> +``` diff --git a/doc/stub/fs.lua b/doc/stub/fs.lua index aa40a30f3..e2d8f0741 100644 --- a/doc/stub/fs.lua +++ b/doc/stub/fs.lua @@ -1,6 +1,4 @@ ---- The FS API allows you to manipulate files and the filesystem. --- --- @module fs +--- @module fs --- Returns true if a path is mounted to the parent filesystem. -- diff --git a/doc/stub/global.lua b/doc/stub/global.lua index 6541da7e5..7bd8a7311 100644 --- a/doc/stub/global.lua +++ b/doc/stub/global.lua @@ -62,7 +62,7 @@ function print(...) end -- @usage printError("Something went wrong!") function printError(...) end ---[[- Reads user input from the terminal, automatically handling arrow keys, +--[[- Reads user input from the terminal. This automatically handles arrow keys, pasting, character replacement, history scrollback, auto-completion, and default values. @@ -110,10 +110,15 @@ the prompt. ]] function read(replaceChar, history, completeFn, default) end ---- The ComputerCraft and Minecraft version of the current computer environment. +--- Stores the current ComputerCraft and Minecraft versions. +-- +-- Outside of Minecraft (for instance, in an emulator) @{_HOST} will contain the +-- emulator's version instead. -- -- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`. --- @usage _HOST +-- @usage Print the current computer's environment. +-- +-- print(_HOST) -- @since 1.76 _HOST = _HOST diff --git a/doc/stub/http.lua b/doc/stub/http.lua index 81ba63753..6ec4db8d3 100644 --- a/doc/stub/http.lua +++ b/doc/stub/http.lua @@ -1,14 +1,13 @@ ---- The http library allows communicating with web servers, sending and --- receiving data from them. +--- Make HTTP requests, sending and receiving data to a remote web server. -- -- @module http -- @since 1.1 +-- @see local_ips To allow accessing servers running on your local network. --- Asynchronously make a HTTP request to the given url. -- --- This returns immediately, a [`http_success`](#http-success-event) or --- [`http_failure`](#http-failure-event) will be queued once the request has --- completed. +-- This returns immediately, a @{http_success} or @{http_failure} will be queued +-- once the request has completed. -- -- @tparam string url The url to request -- @tparam[opt] string body An optional string containing the body of the @@ -112,9 +111,8 @@ function post(...) end --- Asynchronously determine whether a URL can be requested. -- --- If this returns `true`, one should also listen for [`http_check` --- events](#http-check-event) which will container further information about --- whether the URL is allowed or not. +-- If this returns `true`, one should also listen for @{http_check} which will +-- container further information about whether the URL is allowed or not. -- -- @tparam string url The URL to check. -- @treturn true When this url is not invalid. This does not imply that it is @@ -128,9 +126,8 @@ function checkURLAsync(url) end --- Determine whether a URL can be requested. -- --- If this returns `true`, one should also listen for [`http_check` --- events](#http-check-event) which will container further information about --- whether the URL is allowed or not. +-- If this returns `true`, one should also listen for @{http_check} which will +-- container further information about whether the URL is allowed or not. -- -- @tparam string url The URL to check. -- @treturn true When this url is valid and can be requested via @{http.request}. @@ -168,9 +165,8 @@ function websocket(url, headers) end --- Asynchronously open a websocket. -- --- This returns immediately, a [`websocket_success`](#websocket-success-event) --- or [`websocket_failure`](#websocket-failure-event) will be queued once the --- request has completed. +-- This returns immediately, a @{websocket_success} or @{websocket_failure} +-- will be queued once the request has completed. -- -- @tparam string url The websocket url to connect to. This should have the -- `ws://` or `wss://` protocol. diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java index 79bbea53b..12d84fb75 100644 --- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java @@ -30,8 +30,8 @@ import java.util.OptionalLong; import java.util.function.Function; /** - * The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and - * paths. This includes: + * Interact with the computer's files and filesystem, allowing you to manipulate files, directories and paths. This + * includes: * *
    *
  • **Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index 2c4801cf4..de83a06c8 100644 --- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -28,7 +28,7 @@ import java.util.Optional; import static dan200.computercraft.core.apis.TableHelper.*; /** - * The http library allows communicating with web servers, sending and receiving data from them. + * Placeholder description, please ignore. * * @cc.module http * @hidden diff --git a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java index 8600656d6..9d13b6f2d 100644 --- a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java @@ -11,7 +11,7 @@ import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.core.computer.ComputerSide; /** - * Interact with redstone attached to this computer. + * Get and set redstone signals adjacent to this computer. * * The {@link RedstoneAPI} library exposes three "types" of redstone control: * - Binary input/output ({@link #setOutput}/{@link #getInput}): These simply check if a redstone wire has any input or diff --git a/src/main/java/dan200/computercraft/core/apis/TermAPI.java b/src/main/java/dan200/computercraft/core/apis/TermAPI.java index 5821c0e28..716b9ddb4 100644 --- a/src/main/java/dan200/computercraft/core/apis/TermAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/TermAPI.java @@ -16,7 +16,8 @@ import dan200.computercraft.shared.util.Colour; import javax.annotation.Nonnull; /** - * The Terminal API provides functions for writing text to the terminal and monitors, and drawing ASCII graphics. + * Interact with a computer's terminal or monitors, writing text and drawing + * ASCII graphics. * * @cc.module term */ diff --git a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua index 2c9eccc62..7140569d9 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua @@ -1,12 +1,13 @@ ---[[- The Colors API allows you to manipulate sets of colors. +--[[- Constants and functions for colour values, suitable for working with +@{term} and @{redstone}. -This is useful in conjunction with Bundled Cables from the RedPower mod, RedNet -Cables from the MineFactory Reloaded mod, and colors on Advanced Computers and -Advanced Monitors. +This is useful in conjunction with @{redstone.setBundledOutput|Bundled Cables} +from mods like Project Red, and @{term.setTextColour|colors on Advanced +Computers and Advanced Monitors}. -For the non-American English version just replace @{colors} with @{colours} and -it will use the other API, colours which is exactly the same, except in British -English (e.g. @{colors.gray} is spelt @{colours.grey}). +For the non-American English version just replace @{colors} with @{colours}. +This alternative API is exactly the same, except the colours use British English +(e.g. @{colors.gray} is spelt @{colours.grey}). On basic terminals (such as the Computer and Monitor), all the colors are converted to grayscale. This means you can still use all 16 colors on the diff --git a/src/main/resources/data/computercraft/lua/rom/apis/colours.lua b/src/main/resources/data/computercraft/lua/rom/apis/colours.lua index 287d73de9..eea54fd7e 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/colours.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/colours.lua @@ -1,4 +1,4 @@ ---- Colours for lovers of British spelling. +--- An alternative version of @{colors} for lovers of British spelling. -- -- @see colors -- @module colours diff --git a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua index 1c77fa26e..df2ae8f6d 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua @@ -1,21 +1,26 @@ ---- The commands API allows your system to directly execute [Minecraft --- commands][mc] and gather data from the results. --- --- While one may use @{commands.exec} directly to execute a command, the --- commands API also provides helper methods to execute every command. For --- instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. --- --- @{commands.async} provides a similar interface to execute asynchronous --- commands. `commands.async.say("Hi!")` is equivalent to --- `commands.execAsync("Hi!")`. --- --- [mc]: https://minecraft.gamepedia.com/Commands --- --- @module commands --- @usage Set the block above this computer to stone: --- --- commands.setblock("~", "~1", "~", "minecraft:stone") +--[[- Execute [Minecraft commands][mc] and gather data from the results from +a command computer. +:::note +This API is only available on Command computers. It is not accessible to normal +players. +::: + +While one may use @{commands.exec} directly to execute a command, the +commands API also provides helper methods to execute every command. For +instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. + +@{commands.async} provides a similar interface to execute asynchronous +commands. `commands.async.say("Hi!")` is equivalent to +`commands.execAsync("Hi!")`. + +[mc]: https://minecraft.gamepedia.com/Commands + +@module commands +@usage Set the block above this computer to stone: + + commands.setblock("~", "~1", "~", "minecraft:stone") +]] if not commands then error("Cannot load command API on normal computer", 2) end diff --git a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua index 53fc56dbc..c00486b69 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua @@ -1,4 +1,4 @@ ---[[- The Disk API allows you to interact with disk drives. +--[[- Interact with disk drives. These functions can operate on locally attached or remote disk drives. To use a locally attached drive, specify β€œside” as one of the six sides (e.g. `left`); to diff --git a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua index e6f6ea2f8..f157389ba 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua @@ -1,5 +1,5 @@ ---[[- The GPS API provides a method for turtles and computers to retrieve their -own locations. +--[[- Use @{modem|modems} to locate the position of the current turtle or +computers. It broadcasts a PING message over @{rednet} and wait for responses. In order for this system to work, there must be at least 4 computers used as gps hosts which diff --git a/src/main/resources/data/computercraft/lua/rom/apis/help.lua b/src/main/resources/data/computercraft/lua/rom/apis/help.lua index 503555de2..f347b13d2 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/help.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/help.lua @@ -1,4 +1,4 @@ ---- Provides an API to read help files. +--- Find help files on the current computer. -- -- @module help -- @since 1.2 diff --git a/src/main/resources/data/computercraft/lua/rom/apis/keys.lua b/src/main/resources/data/computercraft/lua/rom/apis/keys.lua index aeac84b7a..300cf6eb2 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/keys.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/keys.lua @@ -1,5 +1,4 @@ ---- The Keys API provides a table of numerical codes corresponding to keyboard --- keys, suitable for decoding key events. +--- Constants for all keyboard "key codes", as queued by the @{key} event. -- -- These values are not guaranteed to remain the same between versions. It is -- recommended that you use the constants provided by this file, rather than diff --git a/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua index d0ed2ba24..048ce6ab8 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua @@ -1,5 +1,5 @@ ---- An API for advanced systems which can draw pixels and lines, load and draw --- image files. You can use the `colors` API for easier color manipulation. +--- Utilities for drawing more complex graphics, such as pixels, lines and +-- images. -- -- @module paintutils -- @since 1.45 diff --git a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua index 0fe670c08..170e7f4dc 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua @@ -1,8 +1,8 @@ ---[[- Provides a simple implementation of multitasking. +--[[- A simple way to run several functions at once. Functions are not actually executed simultaniously, but rather this API will -automatically switch between them whenever they yield (eg whenever they call -@{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or +automatically switch between them whenever they yield (e.g. whenever they call +@{coroutine.yield}, or functions that call that - such as @{os.pullEvent} - or functions that call that, etc - basically, anything that causes the function to "pause"). @@ -12,6 +12,27 @@ script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API, etc) can safely be used in one without affecting the event queue accessed by the other. + +:::caution +When using this API, be careful to pass the functions you want to run in +parallel, and _not_ the result of calling those functions. + +For instance, the following is correct: + +```lua +local function do_sleep() sleep(1) end +parallel.waitForAny(do_sleep, rednet.receive) +``` + +but the following is **NOT**: + +```lua +local function do_sleep() sleep(1) end +parallel.waitForAny(do_sleep(), rednet.receive) +``` + +::: + @module parallel @since 1.2 ]] diff --git a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua index 8e8d27a6f..ae4063195 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua @@ -1,4 +1,6 @@ ---[[- Peripherals are blocks (or turtle and pocket computer upgrades) which can +--[[- Find and control peripherals attached to this computer. + +Peripherals are blocks (or turtle and pocket computer upgrades) which can be controlled by a computer. For instance, the @{speaker} peripheral allows a computer to play music and the @{monitor} peripheral allows you to display text in the world. diff --git a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua index d5ed812d8..23d8b3cd8 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua @@ -1,6 +1,6 @@ ---[[- The Rednet API allows computers to communicate between each other by using -@{modem|modems}. It provides a layer of abstraction on top of the main @{modem} -peripheral, making it slightly easier to use. +--[[- Communicate with other computers by using @{modem|modems}. @{rednet} +provides a layer of abstraction on top of the main @{modem} peripheral, making +it slightly easier to use. ## Basic usage In order to send a message between two computers, each computer must have a diff --git a/src/main/resources/data/computercraft/lua/rom/apis/settings.lua b/src/main/resources/data/computercraft/lua/rom/apis/settings.lua index 6ef015178..ce29eaf0c 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/settings.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/settings.lua @@ -1,5 +1,4 @@ ---- The settings API allows to store values and save them to a file for --- persistent configurations for CraftOS and your programs. +--- Read and write configuration options for CraftOS and your programs. -- -- By default, the settings API will load its configuration from the -- `/.settings` file. One can then use @{settings.save} to update the file. diff --git a/src/main/resources/data/computercraft/lua/rom/apis/term.lua b/src/main/resources/data/computercraft/lua/rom/apis/term.lua index 125d7c408..2d2b4afb4 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/term.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/term.lua @@ -1,7 +1,4 @@ ---- The Terminal API provides functions for writing text to the terminal and --- monitors, and drawing ASCII graphics. --- --- @module term +--- @module term local expect = dofile("rom/modules/main/cc/expect.lua").expect diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 43343d7e5..c6dce18e6 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -1,5 +1,4 @@ ---- The @{textutils} API provides helpful utilities for formatting and --- manipulating strings. +--- Helpful utilities for formatting and manipulating strings. -- -- @module textutils -- @since 1.2 diff --git a/src/main/resources/data/computercraft/lua/rom/apis/vector.lua b/src/main/resources/data/computercraft/lua/rom/apis/vector.lua index a5227c853..0b90d373c 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/vector.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/vector.lua @@ -1,4 +1,6 @@ ---- The vector API provides methods to create and manipulate vectors. +--- A basic 3D vector type and some common vector operations. This may be useful +-- when working with coordinates in Minecraft's world (such as those from the +-- @{gps} API). -- -- An introduction to vectors can be found on [Wikipedia][wiki]. -- diff --git a/src/main/resources/data/computercraft/lua/rom/apis/window.lua b/src/main/resources/data/computercraft/lua/rom/apis/window.lua index 724cda155..fd57b79fa 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/window.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/window.lua @@ -1,32 +1,33 @@ ---- The Window API allows easy definition of spaces within the display that can --- be written/drawn to, then later redrawn/repositioned/etc as need be. The API --- itself contains only one function, @{window.create}, which returns the --- windows themselves. --- --- Windows are considered terminal objects - as such, they have access to nearly --- all the commands in the term API (plus a few extras of their own, listed --- within said API) and are valid targets to redirect to. --- --- Each window has a "parent" terminal object, which can be the computer's own --- display, a monitor, another window or even other, user-defined terminal --- objects. Whenever a window is rendered to, the actual screen-writing is --- performed via that parent (or, if that has one too, then that parent, and so --- forth). Bear in mind that the cursor of a window's parent will hence be moved --- around etc when writing a given child window. --- --- Windows retain a memory of everything rendered "through" them (hence acting --- as display buffers), and if the parent's display is wiped, the window's --- content can be easily redrawn later. A window may also be flagged as --- invisible, preventing any changes to it from being rendered until it's --- flagged as visible once more. --- --- A parent terminal object may have multiple children assigned to it, and --- windows may overlap. For example, the Multishell system functions by --- assigning each tab a window covering the screen, each using the starting --- terminal display as its parent, and only one of which is visible at a time. --- --- @module window --- @since 1.6 +--[[- A @{term.Redirect|terminal redirect} occupying a smaller area of an +existing terminal. This allows for easy definition of spaces within the display +that can be written/drawn to, then later redrawn/repositioned/etc as need +be. The API itself contains only one function, @{window.create}, which returns +the windows themselves. + +Windows are considered terminal objects - as such, they have access to nearly +all the commands in the term API (plus a few extras of their own, listed within +said API) and are valid targets to redirect to. + +Each window has a "parent" terminal object, which can be the computer's own +display, a monitor, another window or even other, user-defined terminal +objects. Whenever a window is rendered to, the actual screen-writing is +performed via that parent (or, if that has one too, then that parent, and so +forth). Bear in mind that the cursor of a window's parent will hence be moved +around etc when writing a given child window. + +Windows retain a memory of everything rendered "through" them (hence acting as +display buffers), and if the parent's display is wiped, the window's content can +be easily redrawn later. A window may also be flagged as invisible, preventing +any changes to it from being rendered until it's flagged as visible once more. + +A parent terminal object may have multiple children assigned to it, and windows +may overlap. For example, the Multishell system functions by assigning each tab +a window covering the screen, each using the starting terminal display as its +parent, and only one of which is visible at a time. + +@module window +@since 1.6 +]] local expect = dofile("rom/modules/main/cc/expect.lua").expect diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua index a3fa21d33..281cde7c6 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua @@ -1,5 +1,5 @@ --[[- -Provides utilities for converting between streams of DFPWM audio data and a list of amplitudes. +Convert between streams of DFPWM audio data and a list of amplitudes. DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua index c188e7823..0950bebd7 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua @@ -1,8 +1,8 @@ ---- Provides utilities for working with "nft" images. +--- Read and draw nbt ("Nitrogen Fingers Text") images. -- -- nft ("Nitrogen Fingers Text") is a file format for drawing basic images. -- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured --- text. +-- text as well as simple coloured pixels. -- -- @module cc.image.nft -- @since 1.90.0 diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua index b4337381d..4e7bbdaa1 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua @@ -1,5 +1,5 @@ ---[[- Provides a "pretty printer", for rendering data structures in an -aesthetically pleasing manner. +--[[- A pretty printer for rendering data structures in an aesthetically +pleasing manner. In order to display something using @{cc.pretty}, you build up a series of @{Doc|documents}. These behave a little bit like strings; you can concatenate diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua index e0fa83aa9..28391580c 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua @@ -1,5 +1,5 @@ ---[[- This provides a pure Lua implementation of the builtin @{require} function -and @{package} library. +--[[- A pure Lua implementation of the builtin @{require} function and +@{package} library. Generally you do not need to use this module - it is injected into the every program's environment. However, it may be useful when building a custom shell or diff --git a/src/web/index.tsx b/src/web/index.tsx index e22f7304e..12405cff0 100644 --- a/src/web/index.tsx +++ b/src/web/index.tsx @@ -9,6 +9,8 @@ import exampleNft from "./mount/example.nft"; import exampleAudioLicense from "./mount/example.dfpwm.LICENSE"; import exampleAudioUrl from "./mount/example.dfpwm"; +import "./prism.js"; + const defaultFiles: { [filename: string]: string } = { ".settings": settingsFile, "startup.lua": startupFile, @@ -76,7 +78,9 @@ class Window extends Component { const snippet = element.getAttribute("data-snippet"); if (snippet) this.snippets[snippet] = example; - if (element.getAttribute("data-lua-kind") == "expr") { + // We attempt to pretty-print the result of a function _except_ when the function + // is print. This is pretty ugly, but prevents the confusing trailing "1". + if (element.getAttribute("data-lua-kind") == "expr" && !example.startsWith("print(")) { example = exprTemplate.replace("__expr__", example); } diff --git a/src/web/prism.js b/src/web/prism.js new file mode 100644 index 000000000..c0a0be524 --- /dev/null +++ b/src/web/prism.js @@ -0,0 +1,5 @@ +/* PrismJS 1.28.0 +https://prismjs.com/download.html#themes=prism&languages=ini+toml */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},section:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/}; +!function(e){function n(e){return e.replace(/__/g,(function(){return"(?:[\\w-]+|'[^'\n\r]*'|\"(?:\\\\.|[^\\\\\"\r\n])*\")"}))}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n("(^[\t ]*\\[\\s*(?:\\[\\s*)?)__(?:\\s*\\.\\s*__)*(?=\\s*\\])"),"m"),lookbehind:!0,greedy:!0,alias:"class-name"},key:{pattern:RegExp(n("(^[\t ]*|[{,]\\s*)__(?:\\s*\\.\\s*__)*(?=\\s*=)"),"m"),lookbehind:!0,greedy:!0,alias:"property"},string:{pattern:/"""(?:\\[\s\S]|[^\\])*?"""|'''[\s\S]*?'''|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},date:[{pattern:/\b\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?\b/i,alias:"number"},{pattern:/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/,alias:"number"}],number:/(?:\b0(?:x[\da-zA-Z]+(?:_[\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\b|[-+]?\b\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?\b|[-+]?\b(?:inf|nan)\b/,boolean:/\b(?:false|true)\b/,punctuation:/[.,=[\]{}]/}}(Prism); diff --git a/src/web/styles.css b/src/web/styles.css index ad51a54d2..a69dbe4a2 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -21,7 +21,7 @@ table th { background-color: var(--background-2); } -pre.highlight.highlight-lua { +pre.highlight { position: relative; } From 074793090d8f439dbf10ba85cb29b42db09bba68 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 3 May 2022 11:57:35 +0100 Subject: [PATCH 06/22] Fix Optifine detection I really should have tested this. And not expected Optifine to be normal. --- .../shared/integration/Optifine.java | 39 +++++++++++++++++++ .../peripheral/monitor/MonitorRenderer.java | 4 +- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dan200/computercraft/shared/integration/Optifine.java diff --git a/src/main/java/dan200/computercraft/shared/integration/Optifine.java b/src/main/java/dan200/computercraft/shared/integration/Optifine.java new file mode 100644 index 000000000..4c5a7d432 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/integration/Optifine.java @@ -0,0 +1,39 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.integration; + +/** + * Detect whether Optifine is installed. + */ +public final class Optifine +{ + private static final boolean LOADED; + + static + { + boolean loaded; + try + { + Class.forName( "optifine.Installer", false, Optifine.class.getClassLoader() ); + loaded = true; + } + catch( ReflectiveOperationException | LinkageError ignore ) + { + loaded = false; + } + + LOADED = loaded; + } + + private Optifine() + { + } + + public static boolean isLoaded() + { + return LOADED; + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorRenderer.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorRenderer.java index 116a7cdd9..5998abc1c 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorRenderer.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorRenderer.java @@ -7,7 +7,7 @@ package dan200.computercraft.shared.peripheral.monitor; import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.render.TileEntityMonitorRenderer; -import net.minecraftforge.fml.ModList; +import dan200.computercraft.shared.integration.Optifine; import org.lwjgl.opengl.GL; import javax.annotation.Nonnull; @@ -60,7 +60,7 @@ public enum MonitorRenderer return VBO; } - if( ModList.get().isLoaded( "optifine" ) ) + if( Optifine.isLoaded() ) { ComputerCraft.log.warn( "Optifine is loaded, assuming shaders are being used. Falling back to VBO monitor renderer." ); return VBO; From 79467499e622eb21db981f8cee36a61a165e8321 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 3 May 2022 12:47:13 +0100 Subject: [PATCH 07/22] Use ByteBuffers in term.blit This is about 5-6x faster than using a String as we don't need to allocate and copy multiple times. In the grand scheme of things, still vastly overshadowed by the Lua interpreter, but worth doing. --- .../computercraft/core/apis/TermMethods.java | 7 ++++--- .../computercraft/core/terminal/Terminal.java | 3 ++- .../core/terminal/TextBuffer.java | 15 +++++++++++++++ .../core/terminal/TerminalTest.java | 18 ++++++++++++------ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/apis/TermMethods.java b/src/main/java/dan200/computercraft/core/apis/TermMethods.java index 84c104e34..5692065a6 100644 --- a/src/main/java/dan200/computercraft/core/apis/TermMethods.java +++ b/src/main/java/dan200/computercraft/core/apis/TermMethods.java @@ -14,6 +14,7 @@ import dan200.computercraft.shared.util.StringUtil; import org.apache.commons.lang3.ArrayUtils; import javax.annotation.Nonnull; +import java.nio.ByteBuffer; /** * A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors. @@ -283,9 +284,9 @@ public abstract class TermMethods * } */ @LuaFunction - public final void blit( String text, String textColour, String backgroundColour ) throws LuaException + public final void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour ) throws LuaException { - if( textColour.length() != text.length() || backgroundColour.length() != text.length() ) + if( textColour.remaining() != text.remaining() || backgroundColour.remaining() != text.remaining() ) { throw new LuaException( "Arguments must be the same length" ); } @@ -294,7 +295,7 @@ public abstract class TermMethods synchronized( terminal ) { terminal.blit( text, textColour, backgroundColour ); - terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); + terminal.setCursorPos( terminal.getCursorX() + text.remaining(), terminal.getCursorY() ); } } diff --git a/src/main/java/dan200/computercraft/core/terminal/Terminal.java b/src/main/java/dan200/computercraft/core/terminal/Terminal.java index b8c2b8411..6385a84dc 100644 --- a/src/main/java/dan200/computercraft/core/terminal/Terminal.java +++ b/src/main/java/dan200/computercraft/core/terminal/Terminal.java @@ -11,6 +11,7 @@ import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; import javax.annotation.Nonnull; +import java.nio.ByteBuffer; public class Terminal { @@ -191,7 +192,7 @@ public class Terminal return palette; } - public synchronized void blit( String text, String textColour, String backgroundColour ) + public synchronized void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour ) { int x = cursorX; int y = cursorY; diff --git a/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java b/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java index e1211639b..2809546e0 100644 --- a/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java +++ b/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java @@ -5,6 +5,8 @@ */ package dan200.computercraft.core.terminal; +import java.nio.ByteBuffer; + public class TextBuffer { private final char[] text; @@ -42,6 +44,19 @@ public class TextBuffer } } + public void write( ByteBuffer text, int start ) + { + int pos = start; + start = Math.max( start, 0 ); + int length = text.remaining(); + int end = Math.min( start + length, pos + length ); + end = Math.min( end, this.text.length ); + for( int i = start; i < end; i++ ) + { + this.text[i] = (char) (text.get( i - pos ) & 0xFF); + } + } + public void write( TextBuffer text ) { int end = Math.min( text.length(), this.text.length ); diff --git a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java index 37e033150..a68087980 100644 --- a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java +++ b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java @@ -5,6 +5,7 @@ */ package dan200.computercraft.core.terminal; +import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.shared.util.Colour; import dan200.computercraft.utils.CallCounter; import io.netty.buffer.Unpooled; @@ -293,7 +294,7 @@ class TerminalTest CallCounter callCounter = new CallCounter(); Terminal terminal = new Terminal( 4, 3, callCounter ); - terminal.blit( "test", "1234", "abcd" ); + blit( terminal, "test", "1234", "abcd" ); assertThat( terminal, allOf( textMatches( new String[] { @@ -322,7 +323,7 @@ class TerminalTest terminal.setCursorPos( 2, 1 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, allOf( textMatches( new String[] { @@ -354,13 +355,13 @@ class TerminalTest terminal.setCursorPos( 2, -5 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, old.matches() ); callCounter.assertNotCalled(); terminal.setCursorPos( 2, 5 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, old.matches() ); callCounter.assertNotCalled(); } @@ -584,7 +585,7 @@ class TerminalTest { Terminal writeTerminal = new Terminal( 2, 1 ); - writeTerminal.blit( "hi", "11", "ee" ); + blit( writeTerminal, "hi", "11", "ee" ); writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setTextColour( 3 ); writeTerminal.setBackgroundColour( 5 ); @@ -614,7 +615,7 @@ class TerminalTest void testNbtRoundtrip() { Terminal writeTerminal = new Terminal( 10, 5 ); - writeTerminal.blit( "hi", "11", "ee" ); + blit( writeTerminal, "hi", "11", "ee" ); writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setTextColour( 3 ); writeTerminal.setBackgroundColour( 5 ); @@ -687,6 +688,11 @@ class TerminalTest assertEquals( 5, Terminal.getColour( 'Z', Colour.LIME ) ); } + private static void blit( Terminal terminal, String text, String fg, String bg ) + { + terminal.blit( LuaValues.encode( text ), LuaValues.encode( fg ), LuaValues.encode( bg ) ); + } + private static final class TerminalBufferSnapshot { final String[] textLines; From e2189535b87bdddd05e579f8c3d383b656f73578 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 3 May 2022 18:44:37 +0100 Subject: [PATCH 08/22] Fix several thread-unsafe client registrations Also remove deprecated usage of DeferredWorkQueue. Oh goodness, some of this code is so old now. Fixes #1084 --- .../computercraft/client/ClientRegistry.java | 22 ++++++++++--------- .../dan200/computercraft/shared/Registry.java | 4 +--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/dan200/computercraft/client/ClientRegistry.java b/src/main/java/dan200/computercraft/client/ClientRegistry.java index 362de0760..b9e553b9b 100644 --- a/src/main/java/dan200/computercraft/client/ClientRegistry.java +++ b/src/main/java/dan200/computercraft/client/ClientRegistry.java @@ -122,8 +122,6 @@ public final class ClientRegistry @SubscribeEvent public static void setupClient( FMLClientSetupEvent event ) { - registerContainers(); - // While turtles themselves are not transparent, their upgrades may be. RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() ); RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() ); @@ -138,14 +136,18 @@ public final class ClientRegistry net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new ); net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new ); - registerItemProperty( "state", - ( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(), - Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED - ); - registerItemProperty( "coloured", - ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0, - Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED - ); + event.enqueueWork( () -> { + registerContainers(); + + registerItemProperty( "state", + ( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(), + Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED + ); + registerItemProperty( "coloured", + ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0, + Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED + ); + } ); } @SafeVarargs diff --git a/src/main/java/dan200/computercraft/shared/Registry.java b/src/main/java/dan200/computercraft/shared/Registry.java index 3d838fa58..cf141a0a6 100644 --- a/src/main/java/dan200/computercraft/shared/Registry.java +++ b/src/main/java/dan200/computercraft/shared/Registry.java @@ -82,7 +82,6 @@ import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.common.Mod; @@ -339,12 +338,11 @@ public final class Registry } @SubscribeEvent - @SuppressWarnings( "deprecation" ) public static void init( FMLCommonSetupEvent event ) { NetworkHandler.setup(); - DeferredWorkQueue.runLater( () -> { + event.enqueueWork( () -> { registerProviders(); ArgumentSerializers.register(); registerLoot(); From 7ad613249478d3ae36433b495905de093132dabd Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 3 May 2022 15:55:26 +0100 Subject: [PATCH 09/22] Move VarargArguments factory to VarargArguments itself --- .../dan200/computercraft/core/lua/BasicFunction.java | 2 +- .../computercraft/core/lua/CobaltLuaMachine.java | 5 ----- .../core/lua/ResultInterpreterFunction.java | 2 +- .../computercraft/core/lua/VarargArguments.java | 11 ++++++++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index 60e4d1bc9..ee28bf8d5 100644 --- a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -41,7 +41,7 @@ class BasicFunction extends VarArgFunction @Override public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError { - IArguments arguments = CobaltLuaMachine.toArguments( args ); + IArguments arguments = VarargArguments.of( args ); MethodResult results; try { diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index aaff61ce9..163c322d1 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -424,11 +424,6 @@ public class CobaltLuaMachine implements ILuaMachine return objects; } - static IArguments toArguments( Varargs values ) - { - return values == Constants.NONE ? VarargArguments.EMPTY : new VarargArguments( values ); - } - /** * A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly. */ diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index d7b9ec297..e3652be3f 100644 --- a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -51,7 +51,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction Date: Tue, 3 May 2022 22:58:28 +0100 Subject: [PATCH 10/22] Add a test harness for ComputerThread Geesh, this is nasty. Because ComputerThread is incredibly stateful, and we want to run tests in isolation, we run each test inside its own isolated ClassLoader (and thus ComputerThread instance). Everything else is less nasty, though still a bit ... yuck. We also define a custom ILuaMachine which just runs lambdas[^1], and some utilities for starting those. This is then tied together for four very basic tests. This is sufficient for the changes I want to make, but might be nice to test some more comprehensive stuff later on (e.g. timeouts after pausing). [^1]: Which also means the ILuaMachine implementation can be changed by other mods[^2], if someone wants to have another stab at LuaJIT :p. [^2]: In theory. I doubt its possible in practice because so much is package private. --- .../core/computer/ComputerExecutor.java | 3 +- .../computercraft/core/lua/ILuaMachine.java | 7 + .../computercraft/core/asm/GeneratorTest.java | 2 +- .../core/computer/ComputerThreadTest.java | 107 +++++++++ .../core/computer/FakeComputerManager.java | 219 ++++++++++++++++++ .../core/terminal/TerminalMatchers.java | 6 +- .../core/terminal/TerminalTest.java | 2 +- .../{utils => support}/CallCounter.java | 2 +- .../support/ConcurrentHelpers.java | 56 +++++ .../{ => support}/ContramapMatcher.java | 34 ++- .../computercraft/support/IsolatedRunner.java | 110 +++++++++ src/test/resources/junit-platform.properties | 2 + 12 files changed, 522 insertions(+), 28 deletions(-) create mode 100644 src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java create mode 100644 src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java rename src/test/java/dan200/computercraft/{utils => support}/CallCounter.java (95%) create mode 100644 src/test/java/dan200/computercraft/support/ConcurrentHelpers.java rename src/test/java/dan200/computercraft/{ => support}/ContramapMatcher.java (50%) create mode 100644 src/test/java/dan200/computercraft/support/IsolatedRunner.java create mode 100644 src/test/resources/junit-platform.properties diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index fb59fad7f..c6a3a7a34 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -53,6 +53,7 @@ import java.util.concurrent.locks.ReentrantLock; */ final class ComputerExecutor { + static ILuaMachine.Factory luaFactory = CobaltLuaMachine::new; private static final int QUEUE_LIMIT = 256; private final Computer computer; @@ -400,7 +401,7 @@ final class ComputerExecutor } // Create the lua machine - ILuaMachine machine = new CobaltLuaMachine( computer, timeout ); + ILuaMachine machine = luaFactory.create( computer, timeout ); // Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object. for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api ); diff --git a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java index f08f23788..1b981696a 100644 --- a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java @@ -7,6 +7,8 @@ package dan200.computercraft.core.lua; import dan200.computercraft.api.lua.IDynamicLuaObject; import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.TimeoutState; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -63,4 +65,9 @@ public interface ILuaMachine * Close the Lua machine, aborting any running functions and deleting the internal state. */ void close(); + + interface Factory + { + ILuaMachine create( Computer computer, TimeoutState timeout ); + } } diff --git a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java index 51e98d257..4ebe80167 100644 --- a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java +++ b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import static dan200.computercraft.ContramapMatcher.contramap; +import static dan200.computercraft.support.ContramapMatcher.contramap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java new file mode 100644 index 000000000..7901de7fa --- /dev/null +++ b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java @@ -0,0 +1,107 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.core.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.support.ConcurrentHelpers; +import dan200.computercraft.support.IsolatedRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.closeTo; +import static org.junit.jupiter.api.Assertions.*; + +@Timeout( value = 15 ) +@ExtendWith( IsolatedRunner.class ) +@Execution( ExecutionMode.CONCURRENT ) +public class ComputerThreadTest +{ + @Test + public void testSoftAbort() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + assertFalse( timeout.isSoftAborted(), "Should not start soft-aborted" ); + + long delay = ConcurrentHelpers.waitUntil( () -> { + timeout.refresh(); + return timeout.isSoftAborted(); + } ); + assertThat( "Should be soft aborted", delay * 1e-9, closeTo( 7, 0.5 ) ); + ComputerCraft.log.info( "Slept for {}", delay ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testHardAbort() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + assertFalse( timeout.isHardAborted(), "Should not start soft-aborted" ); + + assertThrows( InterruptedException.class, () -> Thread.sleep( 11_000 ), "Sleep should be hard aborted" ); + assertTrue( timeout.isHardAborted(), "Thread should be hard aborted" ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testNoPauseIfNoOtherMachines() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + boolean didPause = ConcurrentHelpers.waitUntil( () -> { + timeout.refresh(); + return timeout.isPaused(); + }, 5, TimeUnit.SECONDS ); + assertFalse( didPause, "Machine shouldn't have paused within 5s" ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testPauseIfSomeOtherMachine() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + long budget = ComputerThread.scaledPeriod(); + assertEquals( budget, TimeUnit.MILLISECONDS.toNanos( 25 ), "Budget should be 25ms" ); + + long delay = ConcurrentHelpers.waitUntil( () -> { + timeout.refresh(); + return timeout.isPaused(); + } ); + assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.025, 0.01 ) ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.createLoopingComputer(); + + FakeComputerManager.startAndWait( computer ); + } +} diff --git a/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java new file mode 100644 index 000000000..ccbf101ba --- /dev/null +++ b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java @@ -0,0 +1,219 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.core.computer; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.support.IsolatedRunner; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Creates "fake" computers, which just run user-defined tasks rather than Lua code. + * + * Note, this will clobber some parts of the global state. It's recommended you use this inside an {@link IsolatedRunner}. + */ +public class FakeComputerManager +{ + interface Task + { + MachineResult run( TimeoutState state ) throws Exception; + } + + private static final Map> machines = new HashMap<>(); + + private static final Lock errorLock = new ReentrantLock(); + private static final Condition hasError = errorLock.newCondition(); + private static volatile Throwable error; + + static + { + ComputerExecutor.luaFactory = ( computer, timeout ) -> new DummyLuaMachine( timeout, machines.get( computer ) ); + } + + /** + * Create a new computer which pulls from our task queue. + * + * @return The computer. This will not be started yet, you must call {@link Computer#turnOn()} and + * {@link Computer#tick()} to do so. + */ + @Nonnull + public static Computer create() + { + Computer computer = new Computer( new BasicEnvironment(), new Terminal( 51, 19 ), 0 ); + machines.put( computer, new ConcurrentLinkedQueue<>() ); + return computer; + } + + /** + * Create and start a new computer which loops forever. + */ + public static void createLoopingComputer() + { + Computer computer = create(); + enqueueForever( computer, t -> { + Thread.sleep( 100 ); + return MachineResult.OK; + } ); + computer.turnOn(); + computer.tick(); + } + + /** + * Enqueue a task on a computer. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + public static void enqueue( @Nonnull Computer computer, @Nonnull Task task ) + { + machines.get( computer ).offer( task ); + } + + /** + * Enqueue a repeated task on a computer. This is automatically requeued when the task finishes, meaning the task + * queue is never empty. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + private static void enqueueForever( @Nonnull Computer computer, @Nonnull Task task ) + { + machines.get( computer ).offer( t -> { + MachineResult result = task.run( t ); + + enqueueForever( computer, task ); + computer.queueEvent( "some_event", null ); + return result; + } ); + } + + /** + * Sleep for a given period, immediately propagating any exceptions thrown by a computer. + * + * @param delay The duration to sleep for. + * @param unit The time unit the duration is measured in. + * @throws Exception An exception thrown by a running computer. + */ + public static void sleep( long delay, TimeUnit unit ) throws Exception + { + errorLock.lock(); + try + { + rethrowIfNeeded(); + if( hasError.await( delay, unit ) ) rethrowIfNeeded(); + } + finally + { + errorLock.unlock(); + } + } + + /** + * Start a computer and wait for it to finish. + * + * @param computer The computer to wait for. + * @throws Exception An exception thrown by a running computer. + */ + public static void startAndWait( Computer computer ) throws Exception + { + computer.turnOn(); + computer.tick(); + + do + { + sleep( 100, TimeUnit.MILLISECONDS ); + } while( ComputerThread.hasPendingWork() || computer.isOn() ); + + rethrowIfNeeded(); + } + + private static void rethrowIfNeeded() throws Exception + { + if( error == null ) return; + if( error instanceof Exception ) throw (Exception) error; + if( error instanceof Error ) throw (Error) error; + rethrow( error ); + } + + @SuppressWarnings( "unchecked" ) + private static void rethrow( Throwable e ) throws T + { + throw (T) e; + } + + private static class DummyLuaMachine implements ILuaMachine + { + private final TimeoutState state; + private final Queue handleEvent; + + DummyLuaMachine( TimeoutState state, Queue handleEvent ) + { + this.state = state; + this.handleEvent = handleEvent; + } + + @Override + public void addAPI( @Nonnull ILuaAPI api ) + { + } + + @Override + public MachineResult loadBios( @Nonnull InputStream bios ) + { + return MachineResult.OK; + } + + @Override + public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments ) + { + try + { + return handleEvent.remove().run( state ); + } + catch( Throwable e ) + { + errorLock.lock(); + try + { + if( error == null ) + { + error = e; + hasError.signal(); + } + else + { + error.addSuppressed( e ); + } + } + finally + { + errorLock.unlock(); + } + + if( !(e instanceof Exception) && !(e instanceof AssertionError) ) rethrow( e ); + return MachineResult.error( e.getMessage() ); + } + } + + @Override + public void close() + { + } + } +} diff --git a/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java b/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java index e72932472..3359f5c16 100644 --- a/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java +++ b/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java @@ -5,7 +5,7 @@ */ package dan200.computercraft.core.terminal; -import dan200.computercraft.ContramapMatcher; +import dan200.computercraft.support.ContramapMatcher; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -36,11 +36,11 @@ public class TerminalMatchers public static Matcher linesMatchWith( String kind, LineProvider getLine, Matcher[] lines ) { - return new ContramapMatcher<>( kind, terminal -> { + return ContramapMatcher.contramap( Matchers.array( lines ), kind, terminal -> { String[] termLines = new String[terminal.getHeight()]; for( int i = 0; i < termLines.length; i++ ) termLines[i] = getLine.getLine( terminal, i ).toString(); return termLines; - }, Matchers.array( lines ) ); + } ); } @FunctionalInterface diff --git a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java index a68087980..044aca0d8 100644 --- a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java +++ b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java @@ -7,7 +7,7 @@ package dan200.computercraft.core.terminal; import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.shared.util.Colour; -import dan200.computercraft.utils.CallCounter; +import dan200.computercraft.support.CallCounter; import io.netty.buffer.Unpooled; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; diff --git a/src/test/java/dan200/computercraft/utils/CallCounter.java b/src/test/java/dan200/computercraft/support/CallCounter.java similarity index 95% rename from src/test/java/dan200/computercraft/utils/CallCounter.java rename to src/test/java/dan200/computercraft/support/CallCounter.java index 5dbae63f7..213ad13e0 100644 --- a/src/test/java/dan200/computercraft/utils/CallCounter.java +++ b/src/test/java/dan200/computercraft/support/CallCounter.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.utils; +package dan200.computercraft.support; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java b/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java new file mode 100644 index 000000000..640f55700 --- /dev/null +++ b/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java @@ -0,0 +1,56 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + +/** + * Utilities for working with concurrent systems. + */ +public class ConcurrentHelpers +{ + private static final long DELAY = TimeUnit.MILLISECONDS.toNanos( 2 ); + + /** + * Wait until a condition is true, checking the condition every 2ms. + * + * @param isTrue The condition to check + * @return How long we waited for. + */ + public static long waitUntil( BooleanSupplier isTrue ) + { + long start = System.nanoTime(); + while( true ) + { + if( isTrue.getAsBoolean() ) return System.nanoTime() - start; + LockSupport.parkNanos( DELAY ); + } + } + + /** + * Wait until a condition is true or a timeout is elapsed, checking the condition every 2ms. + * + * @param isTrue The condition to check + * @param timeout The delay after which we will timeout. + * @param unit The time unit the duration is measured in. + * @return {@literal true} if the condition was met, {@literal false} if we timed out instead. + */ + public static boolean waitUntil( BooleanSupplier isTrue, long timeout, TimeUnit unit ) + { + long start = System.nanoTime(); + long timeoutNs = unit.toNanos( timeout ); + while( true ) + { + long time = System.nanoTime() - start; + if( isTrue.getAsBoolean() ) return true; + if( time > timeoutNs ) return false; + + LockSupport.parkNanos( DELAY ); + } + } +} diff --git a/src/test/java/dan200/computercraft/ContramapMatcher.java b/src/test/java/dan200/computercraft/support/ContramapMatcher.java similarity index 50% rename from src/test/java/dan200/computercraft/ContramapMatcher.java rename to src/test/java/dan200/computercraft/support/ContramapMatcher.java index 5126ef280..6f09aab96 100644 --- a/src/test/java/dan200/computercraft/ContramapMatcher.java +++ b/src/test/java/dan200/computercraft/support/ContramapMatcher.java @@ -3,42 +3,34 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft; +package dan200.computercraft.support; -import org.hamcrest.Description; +import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; import java.util.function.Function; -public class ContramapMatcher extends TypeSafeDiagnosingMatcher +/** + * Given some function from {@code T} to {@code U}, converts a {@code Matcher} to {@code Matcher}. This is useful + * when you want to match on a particular field (or some other projection) as part of a larger matcher. + * + * @param The type of the object to be matched. + * @param The type of the projection/field to be matched. + */ +public final class ContramapMatcher extends FeatureMatcher { - private final String desc; private final Function convert; - private final Matcher matcher; public ContramapMatcher( String desc, Function convert, Matcher matcher ) { - this.desc = desc; + super( matcher, desc, desc ); this.convert = convert; - this.matcher = matcher; } @Override - protected boolean matchesSafely( T item, Description mismatchDescription ) + protected U featureValueOf( T actual ) { - U converted = convert.apply( item ); - if( matcher.matches( converted ) ) return true; - - mismatchDescription.appendText( desc ).appendText( " " ); - matcher.describeMismatch( converted, mismatchDescription ); - return false; - } - - @Override - public void describeTo( Description description ) - { - description.appendText( desc ).appendText( " " ).appendDescriptionOf( matcher ); + return convert.apply( actual ); } public static Matcher contramap( Matcher matcher, String desc, Function convert ) diff --git a/src/test/java/dan200/computercraft/support/IsolatedRunner.java b/src/test/java/dan200/computercraft/support/IsolatedRunner.java new file mode 100644 index 000000000..5d95fcd00 --- /dev/null +++ b/src/test/java/dan200/computercraft/support/IsolatedRunner.java @@ -0,0 +1,110 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.support; + +import com.google.common.io.ByteStreams; +import net.minecraftforge.fml.unsafe.UnsafeHacks; +import org.junit.jupiter.api.extension.*; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.CodeSource; +import java.security.SecureClassLoader; + +/** + * Runs a test method in an entirely isolated {@link ClassLoader}, so you can mess around with as much of + * {@link dan200.computercraft} as you like. + * + * This IS NOT a good idea, but helps us run some tests in parallel while having lots of (terrible) + * global state. + */ +public class IsolatedRunner implements InvocationInterceptor, BeforeEachCallback, AfterEachCallback +{ + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( new Object() ); + + @Override + public void beforeEach( ExtensionContext context ) throws Exception + { + ClassLoader loader = context.getStore( NAMESPACE ).getOrComputeIfAbsent( IsolatedClassLoader.class ); + + // Rename the global thread group to something more obvious. + ThreadGroup group = (ThreadGroup) loader.loadClass( "dan200.computercraft.shared.util.ThreadUtils" ).getMethod( "group" ).invoke( null ); + Field field = ThreadGroup.class.getDeclaredField( "name" ); + UnsafeHacks.setField( field, group, "<" + context.getDisplayName() + ">" ); + } + + @Override + public void afterEach( ExtensionContext context ) throws Exception + { + ClassLoader loader = context.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class ); + loader.loadClass( "dan200.computercraft.core.computer.ComputerThread" ) + .getDeclaredMethod( "stop" ) + .invoke( null ); + } + + + @Override + public void interceptTestMethod( Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext ) throws Throwable + { + invocation.skip(); + + ClassLoader loader = extensionContext.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class ); + Method method = invocationContext.getExecutable(); + + Class ourClass = loader.loadClass( method.getDeclaringClass().getName() ); + Method ourMethod = ourClass.getDeclaredMethod( method.getName(), method.getParameterTypes() ); + + try + { + ourMethod.invoke( ourClass.getConstructor().newInstance(), invocationContext.getArguments().toArray() ); + } + catch( InvocationTargetException e ) + { + throw e.getTargetException(); + } + } + + private static class IsolatedClassLoader extends SecureClassLoader + { + IsolatedClassLoader() + { + super( IsolatedClassLoader.class.getClassLoader() ); + } + + @Override + public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException + { + synchronized( getClassLoadingLock( name ) ) + { + Class c = findLoadedClass( name ); + if( c != null ) return c; + + if( name.startsWith( "dan200.computercraft." ) ) + { + CodeSource parentSource = getParent().loadClass( name ).getProtectionDomain().getCodeSource(); + + byte[] contents; + try( InputStream stream = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) + { + if( stream == null ) throw new ClassNotFoundException( name ); + contents = ByteStreams.toByteArray( stream ); + } + catch( IOException e ) + { + throw new ClassNotFoundException( name, e ); + } + + return defineClass( name, contents, 0, contents.length, parentSource ); + } + } + + return super.loadClass( name, resolve ); + } + } +} diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..33ac738be --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.dynamic.factor=4 From 03b0244084755b955cd427ba49f9c16bb8417c65 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 4 May 2022 11:31:39 +0100 Subject: [PATCH 11/22] Minor improvements to resetting code - Reset state while the server is starting rather than once it has started. Hopefully fixes a weird issue where wireless modems wouldn't be "connected" on server startup. - Correctly reset the MainThread metrics on server start/stop. --- .../computercraft/core/computer/MainThread.java | 4 ++-- .../dan200/computercraft/shared/CommonHooks.java | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/computer/MainThread.java b/src/main/java/dan200/computercraft/core/computer/MainThread.java index c1658aa67..222abb274 100644 --- a/src/main/java/dan200/computercraft/core/computer/MainThread.java +++ b/src/main/java/dan200/computercraft/core/computer/MainThread.java @@ -23,8 +23,8 @@ import java.util.concurrent.atomic.AtomicLong; * {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks * (those run by tile entities, etc...) will also consume the budget * - * Next tick, we put {@link ComputerCraft#maxMainGlobalTime} into our budget (and clamp it to that value to). If we're - * still over budget, then we should not execute any work (either as part of {@link MainThread} or externally). + * Next tick, we add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still + * over budget, then we should not execute any work (either as part of {@link MainThread} or externally). */ public final class MainThread { diff --git a/src/main/java/dan200/computercraft/shared/CommonHooks.java b/src/main/java/dan200/computercraft/shared/CommonHooks.java index 842a8af3d..864772eba 100644 --- a/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -29,7 +29,6 @@ import net.minecraftforge.event.*; import net.minecraftforge.event.entity.player.PlayerContainerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; @@ -90,22 +89,21 @@ public final class CommonHooks { ComputerMBean.register(); } - } - @SubscribeEvent - public static void onServerStarted( FMLServerStartedEvent event ) - { - ComputerCraft.serverComputerRegistry.reset(); - WirelessNetwork.resetNetworks(); - Tracking.reset(); + resetState(); ComputerMBean.registerTracker(); - NetworkUtils.reset(); } @SubscribeEvent public static void onServerStopped( FMLServerStoppedEvent event ) + { + resetState(); + } + + private static void resetState() { ComputerCraft.serverComputerRegistry.reset(); + MainThread.reset(); WirelessNetwork.resetNetworks(); Tracking.reset(); NetworkUtils.reset(); From 65a7370db1e967025e45225a97d0b4585c341480 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 4 May 2022 12:30:12 +0100 Subject: [PATCH 12/22] Rethink how computer timeouts are handled Previously we would compute the current timeout flags every 128 instructions of the Lua machine. While computing these flags is _relatively_ cheap (just get the current time), it still all adds up. Instead we now set the timeout flags from the computer monitor/watchdog thread. This does mean that the monitor thread needs to wake up more often[^1] _if the queue is full_, otherwise we can sleep for 100ms as before. This does mean that pausing is a little less accurate (can technically take up 2*period instead). This isn't great, but in practice it seems fine - I've not noticed any playability issues. This offers a small (but measurable!) boost to computer performance. [^1]: We currently sleep for scaledPeriod, but we could choose to do less. --- .../core/computer/ComputerThread.java | 219 +++++++++++------- .../core/computer/TimeoutState.java | 6 +- .../core/lua/CobaltLuaMachine.java | 39 ++-- .../core/computer/ComputerThreadTest.java | 17 +- 4 files changed, 159 insertions(+), 122 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java index 028b7b056..d557fabe5 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java @@ -13,6 +13,7 @@ import javax.annotation.Nullable; import java.util.TreeSet; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.LockSupport; @@ -49,11 +50,11 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT; public final class ComputerThread { /** - * How often the computer thread monitor should run, in milliseconds. + * How often the computer thread monitor should run. * * @see Monitor */ - private static final int MONITOR_WAKEUP = 100; + private static final long MONITOR_WAKEUP = TimeUnit.MILLISECONDS.toNanos( 100 ); /** * The target latency between executing two tasks on a single machine. @@ -76,6 +77,13 @@ public final class ComputerThread */ private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD; + /** + * Time difference between reporting crashed threads. + * + * @see TaskRunner#reportTimeout(ComputerExecutor, long) + */ + private static final long REPORT_DEBOUNCE = TimeUnit.SECONDS.toNanos( 1 ); + /** * Lock used for modifications to the array of current threads. */ @@ -102,6 +110,8 @@ public final class ComputerThread private static final ReentrantLock computerLock = new ReentrantLock(); private static final Condition hasWork = computerLock.newCondition(); + private static final AtomicInteger idleWorkers = new AtomicInteger( 0 ); + private static final Condition monitorWakeup = computerLock.newCondition(); /** * Active queues to execute. @@ -135,7 +145,7 @@ public final class ComputerThread if( runners == null ) { - // TODO: Change the runners length on config reloads + // TODO: Update this on config reloads. Or possibly on world restarts? runners = new TaskRunner[ComputerCraft.computerThreads]; // latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for @@ -227,9 +237,14 @@ public final class ComputerThread executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime ); + boolean wasBusy = isBusy(); // Add to the queue, and signal the workers. computerQueue.add( executor ); hasWork.signal(); + + // If we've transitioned into a busy state, notify the monitor. This will cause it to sleep for scaledPeriod + // instead of the longer wakeup duration. + if( !wasBusy && isBusy() ) monitorWakeup.signal(); } finally { @@ -346,6 +361,17 @@ public final class ComputerThread return !computerQueue.isEmpty(); } + /** + * Check if we have more work queued than we have capacity for. Effectively a more fine-grained version of + * {@link #hasPendingWork()}. + * + * @return If the computer threads are busy. + */ + private static boolean isBusy() + { + return computerQueue.size() > idleWorkers.get(); + } + /** * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard * abort limit. @@ -357,76 +383,93 @@ public final class ComputerThread @Override public void run() { - try + while( true ) { - while( true ) + computerLock.lock(); + try { - Thread.sleep( MONITOR_WAKEUP ); + // If we've got more work than we have capacity for it, then we'll need to pause a task soon, so + // sleep for a single pause duration. Otherwise we only need to wake up to set the soft/hard abort + // flags, which are far less granular. + monitorWakeup.awaitNanos( isBusy() ? scaledPeriod() : MONITOR_WAKEUP ); + } + catch( InterruptedException e ) + { + ComputerCraft.log.error( "Monitor thread interrupted. Computers may behave very badly!", e ); + break; + } + finally + { + computerLock.unlock(); + } - TaskRunner[] currentRunners = ComputerThread.runners; - if( currentRunners != null ) + checkRunners(); + } + } + + private static void checkRunners() + { + TaskRunner[] currentRunners = ComputerThread.runners; + if( currentRunners == null ) return; + + for( int i = 0; i < currentRunners.length; i++ ) + { + TaskRunner runner = currentRunners[i]; + // If we've no runner, skip. + if( runner == null || runner.owner == null || !runner.owner.isAlive() ) + { + if( !running ) continue; + + // Mark the old runner as dead and start a new one. + ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!", + runner != null && runner.owner != null ? runner.owner.getName() : runner ); + if( runner != null ) runner.running = false; + runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); + } + + // If the runner has no work, skip + ComputerExecutor executor = runner.currentExecutor.get(); + if( executor == null ) continue; + + // Refresh the timeout state. Will set the pause/soft timeout flags as appropriate. + executor.timeout.refresh(); + + // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), + // then we can let the Lua machine do its work. + long afterStart = executor.timeout.nanoCumulative(); + long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; + if( afterHardAbort < 0 ) continue; + + // Set the hard abort flag. + executor.timeout.hardAbort(); + executor.abort(); + + if( afterHardAbort >= ABORT_TIMEOUT * 2 ) + { + // If we've hard aborted and interrupted, and we're still not dead, then mark the runner + // as dead, finish off the task, and spawn a new runner. + runner.reportTimeout( executor, afterStart ); + runner.running = false; + runner.owner.interrupt(); + + ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); + if( thisExecutor != null ) afterWork( runner, executor ); + + synchronized( threadLock ) { - for( int i = 0; i < currentRunners.length; i++ ) + if( running && runners.length > i && runners[i] == runner ) { - TaskRunner runner = currentRunners[i]; - // If we've no runner, skip. - if( runner == null || runner.owner == null || !runner.owner.isAlive() ) - { - if( !running ) continue; - - // Mark the old runner as dead and start a new one. - ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!", - runner != null && runner.owner != null ? runner.owner.getName() : runner ); - if( runner != null ) runner.running = false; - runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); - } - - // If the runner has no work, skip - ComputerExecutor executor = runner.currentExecutor.get(); - if( executor == null ) continue; - - // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), - // then we can let the Lua machine do its work. - long afterStart = executor.timeout.nanoCumulative(); - long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; - if( afterHardAbort < 0 ) continue; - - // Set the hard abort flag. - executor.timeout.hardAbort(); - executor.abort(); - - if( afterHardAbort >= ABORT_TIMEOUT * 2 ) - { - // If we've hard aborted and interrupted, and we're still not dead, then mark the runner - // as dead, finish off the task, and spawn a new runner. - timeoutTask( executor, runner.owner, afterStart ); - runner.running = false; - runner.owner.interrupt(); - - ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); - if( thisExecutor != null ) afterWork( runner, executor ); - - synchronized( threadLock ) - { - if( running && runners.length > i && runners[i] == runner ) - { - runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start(); - } - } - } - else if( afterHardAbort >= ABORT_TIMEOUT ) - { - // If we've hard aborted but we're still not dead, dump the stack trace and interrupt - // the task. - timeoutTask( executor, runner.owner, afterStart ); - runner.owner.interrupt(); - } + runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start(); } } } - } - catch( InterruptedException ignored ) - { + else if( afterHardAbort >= ABORT_TIMEOUT ) + { + // If we've hard aborted but we're still not dead, dump the stack trace and interrupt + // the task. + runner.reportTimeout( executor, afterStart ); + runner.owner.interrupt(); + } } } } @@ -441,6 +484,7 @@ public final class ComputerThread private static final class TaskRunner implements Runnable { Thread owner; + long lastReport = Long.MIN_VALUE; volatile boolean running = true; final AtomicReference currentExecutor = new AtomicReference<>(); @@ -460,6 +504,7 @@ public final class ComputerThread computerLock.lockInterruptibly(); try { + idleWorkers.incrementAndGet(); while( computerQueue.isEmpty() ) hasWork.await(); executor = computerQueue.pollFirst(); assert executor != null : "hasWork should ensure we never receive null work"; @@ -467,6 +512,7 @@ public final class ComputerThread finally { computerLock.unlock(); + idleWorkers.decrementAndGet(); } } catch( InterruptedException ignored ) @@ -516,27 +562,32 @@ public final class ComputerThread } } } - } - private static void timeoutTask( ComputerExecutor executor, Thread thread, long time ) - { - if( !ComputerCraft.logComputerErrors ) return; - - StringBuilder builder = new StringBuilder() - .append( "Terminating computer #" ).append( executor.getComputer().getID() ) - .append( " due to timeout (running for " ).append( time * 1e-9 ) - .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) - .append( thread.getName() ) - .append( " is currently " ) - .append( thread.getState() ); - Object blocking = LockSupport.getBlocker( thread ); - if( blocking != null ) builder.append( "\n on " ).append( blocking ); - - for( StackTraceElement element : thread.getStackTrace() ) + private void reportTimeout( ComputerExecutor executor, long time ) { - builder.append( "\n at " ).append( element ); - } + if( !ComputerCraft.logComputerErrors ) return; - ComputerCraft.log.warn( builder.toString() ); + // Attempt to debounce stack trace reporting, limiting ourselves to one every second. + long now = System.nanoTime(); + if( lastReport != Long.MIN_VALUE && now - lastReport - REPORT_DEBOUNCE <= 0 ) return; + lastReport = now; + + StringBuilder builder = new StringBuilder() + .append( "Terminating computer #" ).append( executor.getComputer().getID() ) + .append( " due to timeout (running for " ).append( time * 1e-9 ) + .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) + .append( owner.getName() ) + .append( " is currently " ) + .append( owner.getState() ); + Object blocking = LockSupport.getBlocker( owner ); + if( blocking != null ) builder.append( "\n on " ).append( blocking ); + + for( StackTraceElement element : owner.getStackTrace() ) + { + builder.append( "\n at " ).append( element ); + } + + ComputerCraft.log.warn( builder.toString() ); + } } } diff --git a/src/main/java/dan200/computercraft/core/computer/TimeoutState.java b/src/main/java/dan200/computercraft/core/computer/TimeoutState.java index 875bf6788..80be47199 100644 --- a/src/main/java/dan200/computercraft/core/computer/TimeoutState.java +++ b/src/main/java/dan200/computercraft/core/computer/TimeoutState.java @@ -86,7 +86,7 @@ public final class TimeoutState /** * Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags. */ - public void refresh() + public synchronized void refresh() { // Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we // need to handle overflow. @@ -153,7 +153,7 @@ public final class TimeoutState * * @see #nanoCumulative() */ - void pauseTimer() + synchronized void pauseTimer() { // We set the cumulative time to difference between current time and "nominal start time". cumulativeElapsed = System.nanoTime() - cumulativeStart; @@ -163,7 +163,7 @@ public final class TimeoutState /** * Resets the cumulative time and resets the abort flags. */ - void stopTimer() + synchronized void stopTimer() { cumulativeElapsed = 0; paused = softAbort = hardAbort = false; diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index 163c322d1..555466a20 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -452,24 +452,9 @@ public class CobaltLuaMachine implements ILuaMachine // We check our current pause/abort state every 128 instructions. if( (count = (count + 1) & 127) == 0 ) { - // If we've been hard aborted or closed then abort. if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; - - timeout.refresh(); - if( timeout.isPaused() ) - { - // Preserve the current state - isPaused = true; - oldInHook = ds.inhook; - oldFlags = di.flags; - - // Suspend the state. This will probably throw, but we need to handle the case where it won't. - di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; - LuaThread.suspend( ds.getLuaState() ); - resetPaused( ds, di ); - } - - handleSoftAbort(); + if( timeout.isPaused() ) handlePause( ds, di ); + if( timeout.isSoftAborted() ) handleSoftAbort(); } super.onInstruction( ds, di, pc ); @@ -478,13 +463,10 @@ public class CobaltLuaMachine implements ILuaMachine @Override public void poll() throws LuaError { - // If we've been hard aborted or closed then abort. LuaState state = CobaltLuaMachine.this.state; if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; - - timeout.refresh(); if( timeout.isPaused() ) LuaThread.suspendBlocking( state ); - handleSoftAbort(); + if( timeout.isSoftAborted() ) handleSoftAbort(); } private void resetPaused( DebugState ds, DebugFrame di ) @@ -498,11 +480,24 @@ public class CobaltLuaMachine implements ILuaMachine private void handleSoftAbort() throws LuaError { // If we already thrown our soft abort error then don't do it again. - if( !timeout.isSoftAborted() || thrownSoftAbort ) return; + if( thrownSoftAbort ) return; thrownSoftAbort = true; throw new LuaError( TimeoutState.ABORT_MESSAGE ); } + + private void handlePause( DebugState ds, DebugFrame di ) throws LuaError, UnwindThrowable + { + // Preserve the current state + isPaused = true; + oldInHook = ds.inhook; + oldFlags = di.flags; + + // Suspend the state. This will probably throw, but we need to handle the case where it won't. + di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; + LuaThread.suspend( ds.getLuaState() ); + resetPaused( ds, di ); + } } private static final class HardAbortError extends Error diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java index 7901de7fa..70fcc3422 100644 --- a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java +++ b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java @@ -33,10 +33,7 @@ public class ComputerThreadTest FakeComputerManager.enqueue( computer, timeout -> { assertFalse( timeout.isSoftAborted(), "Should not start soft-aborted" ); - long delay = ConcurrentHelpers.waitUntil( () -> { - timeout.refresh(); - return timeout.isSoftAborted(); - } ); + long delay = ConcurrentHelpers.waitUntil( timeout::isSoftAborted ); assertThat( "Should be soft aborted", delay * 1e-9, closeTo( 7, 0.5 ) ); ComputerCraft.log.info( "Slept for {}", delay ); @@ -69,10 +66,7 @@ public class ComputerThreadTest { Computer computer = FakeComputerManager.create(); FakeComputerManager.enqueue( computer, timeout -> { - boolean didPause = ConcurrentHelpers.waitUntil( () -> { - timeout.refresh(); - return timeout.isPaused(); - }, 5, TimeUnit.SECONDS ); + boolean didPause = ConcurrentHelpers.waitUntil( timeout::isPaused, 5, TimeUnit.SECONDS ); assertFalse( didPause, "Machine shouldn't have paused within 5s" ); computer.shutdown(); @@ -90,11 +84,8 @@ public class ComputerThreadTest long budget = ComputerThread.scaledPeriod(); assertEquals( budget, TimeUnit.MILLISECONDS.toNanos( 25 ), "Budget should be 25ms" ); - long delay = ConcurrentHelpers.waitUntil( () -> { - timeout.refresh(); - return timeout.isPaused(); - } ); - assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.025, 0.01 ) ); + long delay = ConcurrentHelpers.waitUntil( timeout::isPaused ); + assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.03, 0.015 ) ); computer.shutdown(); return MachineResult.OK; From be45b718b32fb80f29e1e9ccba06a515bf3144b1 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 5 May 2022 00:23:38 +0100 Subject: [PATCH 13/22] Correctly mark reify as CLIENT --- .../shared/peripheral/speaker/SpeakerPosition.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPosition.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPosition.java index 6331e76d3..c67694c35 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPosition.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPosition.java @@ -11,6 +11,8 @@ import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -102,6 +104,7 @@ public final class SpeakerPosition } @Nonnull + @OnlyIn( Dist.CLIENT ) public SpeakerPosition reify() { Minecraft minecraft = Minecraft.getInstance(); From 87a1c1a525ea97e38fbcb61253d97c7b1a6509b9 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 5 May 2022 13:24:02 +0100 Subject: [PATCH 14/22] Some minor documentation fixes - Add a TOC to the Local IPs page. - Increase the echo delay in our speaker audio page to 1.5s. This sounds much better and is less clashy than 1s. Also add a sleep(0) (eww, I know) to fix timeouts on some browsers/computers. - Move Lua feature compat to a new "reference" section. Still haven't figured out how to structure these docs - open to any ideas really. - Mention FFmpeg as an option for converting to DFPWM (closes #1075). - Allow data-mount to override built-in files. See my comment in #1069. --- doc/guides/local_ips.md | 6 +++++- doc/guides/speaker_audio.md | 19 ++++++++++++------- doc/{guides => reference}/feature_compat.md | 2 +- illuaminate.sexp | 7 +++---- .../peripheral/speaker/SpeakerPeripheral.java | 2 +- .../lua/rom/modules/main/cc/audio/dfpwm.lua | 5 +++-- src/web/index.tsx | 2 +- 7 files changed, 26 insertions(+), 17 deletions(-) rename doc/{guides => reference}/feature_compat.md (99%) diff --git a/doc/guides/local_ips.md b/doc/guides/local_ips.md index c8adf74ce..affad7355 100644 --- a/doc/guides/local_ips.md +++ b/doc/guides/local_ips.md @@ -2,11 +2,15 @@ module: [kind=guide] local_ips --- -# Allowing access to local IPS +# Allowing access to local IPs By default, ComputerCraft blocks access to local IP addresses for security. This means you can't normally access any HTTP server running on your computer. However, this may be useful for testing programs without having a remote server. You can unblock these IPs in the ComputerCraft config. + - [Minecraft 1.13 and later, CC:T 1.87.0 and later](#cc-1.87.0) + - [Minecraft 1.13 and later, CC:T 1.86.2 and earlier](#cc-1.86.2) + - [Minecraft 1.12.2 and earlier](#mc-1.12) + ## Minecraft 1.13 and later, CC:T 1.87.0 and later {#cc-1.87.0} The configuration file can be located at `serverconfig/computercraft-server.toml` inside the world folder on either single-player or multiplayer. Look for lines that look like this: diff --git a/doc/guides/speaker_audio.md b/doc/guides/speaker_audio.md index 0f9652ae4..4cbe9e437 100644 --- a/doc/guides/speaker_audio.md +++ b/doc/guides/speaker_audio.md @@ -125,7 +125,7 @@ different. First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker. -As mentioned to above, @{speaker.playAudio} accepts at most 128Γ—1024 samples in one go. DFPMW uses a single bit for each +As mentioned above, @{speaker.playAudio} accepts at most 128Γ—1024 samples in one go. DFPMW uses a single bit for each sample, which means we want to process our audio in chunks of 16Γ—1024 bytes (16KiB). In order to do this, we use @{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and @{fs.BinaryReadHandle.read} if you prefer. @@ -136,22 +136,22 @@ You can mix together samples from different streams by adding their amplitudes, samples, etc... Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you -hear a faint echo about a second later. +hear a faint echo a second and a half later. In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it. However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and -adds the sample from one second ago to it. +adds the sample from 1.5 seconds ago to it. -For this, we'll need to keep track of the last 48k samples - exactly one seconds worth of audio. We can do this using a +For this, we'll need to keep track of the last 72k samples - exactly 1.5 seconds worth of audio. We can do this using a [Ring Buffer], which helps makes things a little more efficient. ```lua {data-peripheral=speaker} local dfpwm = require("cc.audio.dfpwm") local speaker = peripheral.find("speaker") --- Speakers play at 48kHz, so one second is 48k samples. We first fill our buffer +-- Speakers play at 48kHz, so 1.5 seconds is 72k samples. We first fill our buffer -- with 0s, as there's nothing to echo at the start of the track! -local samples_i, samples_n = 1, 48000 +local samples_i, samples_n = 1, 48000 * 1.5 local samples = {} for i = 1, samples_n do samples[i] = 0 end @@ -162,7 +162,7 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do for i = 1, #buffer do local original_value = buffer[i] - -- Replace this sample with its current amplitude plus the amplitude from one second ago. + -- Replace this sample with its current amplitude plus the amplitude from 1.5 seconds ago. -- We scale both to ensure the resulting value is still between -128 and 127. buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4 @@ -175,6 +175,11 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end + + -- The audio processing above can be quite slow and preparing the first batch of audio + -- may timeout the computer. We sleep to avoid this. + -- There's definitely better ways of handling this - this is just an example! + sleep(0.05) end ``` diff --git a/doc/guides/feature_compat.md b/doc/reference/feature_compat.md similarity index 99% rename from doc/guides/feature_compat.md rename to doc/reference/feature_compat.md index a72f5046f..d0b03cf45 100644 --- a/doc/guides/feature_compat.md +++ b/doc/reference/feature_compat.md @@ -1,5 +1,5 @@ --- -module: [kind=guide] feature_compat +module: [kind=reference] feature_compat --- # Lua 5.2/5.3 features in CC: Tweaked diff --git a/illuaminate.sexp b/illuaminate.sexp index 91d1e99ef..7ddafe9e5 100644 --- a/illuaminate.sexp +++ b/illuaminate.sexp @@ -1,9 +1,7 @@ ; -*- mode: Lisp;-*- (sources - /doc/events/ - /doc/guides/ - /doc/stub/ + /doc/ /build/docs/luaJavadoc/ /src/main/resources/*/computercraft/lua/bios.lua /src/main/resources/*/computercraft/lua/rom/ @@ -29,7 +27,8 @@ (peripheral Peripherals) (generic_peripheral "Generic Peripherals") (event Events) - (guide Guides)) + (guide Guides) + (reference Reference)) (library-path /doc/stub/ diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index 56ff2c96b..65ba23dfc 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -302,7 +302,7 @@ public abstract class SpeakerPeripheral implements IPeripheral * computer is lagging. * ::: * - * {@literal @}{speaker_audio} provides a more complete guide in to using speakers + * {@literal @}{speaker_audio} provides a more complete guide to using speakers * * @param context The Lua context. * @param audio The audio data to play. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua index 281cde7c6..2b5e2d183 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua @@ -18,11 +18,12 @@ for each one you write. ## Converting audio to DFPWM DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it. -Instead, you can convert audio files online using [music.madefor.cc] or with the [LionRay Wav Converter][LionRay] Java -application. +Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java +application or development builds of [FFmpeg]. [music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked" [LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter " +[FFmpeg]: https://ffmpeg.org "FFmpeg command-line audio manipulation library" @see guide!speaker_audio Gives a more general introduction to audio processing and the speaker. @see speaker.playAudio To play the decoded audio data. diff --git a/src/web/index.tsx b/src/web/index.tsx index 12405cff0..a9a4a8ceb 100644 --- a/src/web/index.tsx +++ b/src/web/index.tsx @@ -102,7 +102,7 @@ class Window extends Component {
    :
    ; From f5f0c7990a7d6820fa957adfb9c6076b676222be Mon Sep 17 00:00:00 2001 From: JackMacWindows Date: Sat, 7 May 2022 06:10:25 -0400 Subject: [PATCH 15/22] Add note about special JSON values in docs for `textutils.unserializeJSON` (#1058) --- .../data/computercraft/lua/rom/apis/textutils.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index c6dce18e6..bd5d2f606 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -657,6 +657,9 @@ do -- This may be used with @{textutils.serializeJSON}, or when communicating -- with command blocks or web APIs. -- + -- If a `null` value is encountered, it is converted into @{textutils.json_null}. + -- If an empty array is encountered, it is converted into @{textutils.empty_json_array}. + -- -- @tparam string s The serialised string to deserialise. -- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options -- Options which control how this JSON object is parsed. @@ -671,6 +674,8 @@ do -- @treturn[2] nil If the object could not be deserialised. -- @treturn string A message describing why the JSON string is invalid. -- @since 1.87.0 + -- @see textutils.json_null Use to serialize a JSON `null` value. + -- @see textutils.empty_json_array Use to serialize a JSON empty array. unserialise_json = function(s, options) expect(1, s, "string") expect(2, options, "table", "nil") @@ -784,6 +789,8 @@ unserialise = unserialize -- GB version -- times. -- @usage textutils.serializeJSON({ values = { 1, "2", true } }) -- @since 1.7 +-- @see textutils.json_null Use to serialize a JSON `null` value. +-- @see textutils.empty_json_array Use to serialize a JSON empty array. function serializeJSON(t, bNBTStyle) expect(1, t, "table", "string", "number", "boolean") expect(2, bNBTStyle, "boolean", "nil") From 78334c4cb1c4cc92d3203fa319af3bc8f9745d3a Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 7 May 2022 20:15:29 +0100 Subject: [PATCH 16/22] Fix counts in /computercraft {turn-on,shutdown} We were using the size of the selectors (which is normally 1) rather than the number of computers. --- .../shared/command/CommandComputerCraft.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java index f26284228..70e000046 100644 --- a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java +++ b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java @@ -141,9 +141,10 @@ public final class CommandComputerCraft .then( command( "shutdown" ) .requires( UserLevel.OWNER_OP ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) - .executes( ( context, computers ) -> { + .executes( ( context, computerSelectors ) -> { int shutdown = 0; - for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + Set computers = unwrap( context.getSource(), computerSelectors ); + for( ServerComputer computer : computers ) { if( computer.isOn() ) shutdown++; computer.shutdown(); @@ -155,9 +156,10 @@ public final class CommandComputerCraft .then( command( "turn-on" ) .requires( UserLevel.OWNER_OP ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) - .executes( ( context, computers ) -> { + .executes( ( context, computerSelectors ) -> { int on = 0; - for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + Set computers = unwrap( context.getSource(), computerSelectors ); + for( ServerComputer computer : computers ) { if( !computer.isOn() ) on++; computer.turnOn(); From d9e75d7c47282de6a1781be47aa2365e1e6b6143 Mon Sep 17 00:00:00 2001 From: Chick Chicky <91527355+ChickChicky@users.noreply.github.com> Date: Sun, 8 May 2022 11:53:02 +0200 Subject: [PATCH 17/22] Added parse_empty_array to textutils.unserialiseJSON (#1092) Fixes #1089. --- .../computercraft/lua/rom/apis/textutils.lua | 72 ++++++++++++------- .../test-rom/spec/apis/textutils_spec.lua | 11 ++- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index bd5d2f606..e5bc459f5 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -631,7 +631,13 @@ do end if c == "" then return expected(pos, c, "']'") end - if c == "]" then return empty_json_array, pos + 1 end + if c == "]" then + if opts.parse_empty_array ~= false then + return empty_json_array, pos + 1 + else + return {}, pos + 1 + end + end while true do n, arr[n], pos = n + 1, decode_impl(str, pos, opts) @@ -652,37 +658,51 @@ do error_at(pos, "Unexpected character %q.", c) end - --- Converts a serialised JSON string back into a reassembled Lua object. - -- - -- This may be used with @{textutils.serializeJSON}, or when communicating - -- with command blocks or web APIs. - -- - -- If a `null` value is encountered, it is converted into @{textutils.json_null}. - -- If an empty array is encountered, it is converted into @{textutils.empty_json_array}. - -- - -- @tparam string s The serialised string to deserialise. - -- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options - -- Options which control how this JSON object is parsed. - -- - -- - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, - -- as produced by many commands. - -- - `parse_null`: When true, `null` will be parsed as @{json_null}, rather - -- than `nil`. - -- - -- [nbt]: https://minecraft.gamepedia.com/NBT_format - -- @return[1] The deserialised object - -- @treturn[2] nil If the object could not be deserialised. - -- @treturn string A message describing why the JSON string is invalid. - -- @since 1.87.0 - -- @see textutils.json_null Use to serialize a JSON `null` value. - -- @see textutils.empty_json_array Use to serialize a JSON empty array. + --[[- Converts a serialised JSON string back into a reassembled Lua object. + + This may be used with @{textutils.serializeJSON}, or when communicating + with command blocks or web APIs. + + If a `null` value is encountered, it is converted into `nil`. It can be converted + into @{textutils.json_null} with the `parse_null` option. + + If an empty array is encountered, it is converted into @{textutils.empty_json_array}. + It can be converted into a new empty table with the `parse_empty_array` option. + + @tparam string s The serialised string to deserialise. + @tparam[opt] { nbt_style? = boolean, parse_null? = boolean, parse_empty_array? = boolean } options + Options which control how this JSON object is parsed. + + - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, + as produced by many commands. + - `parse_null`: When true, `null` will be parsed as @{json_null}, rather than + `nil`. + - `parse_empty_array`: When false, empty arrays will be parsed as a new table. + By default (or when this value is true), they are parsed as @{empty_json_array}. + + [nbt]: https://minecraft.gamepedia.com/NBT_format + @return[1] The deserialised object + @treturn[2] nil If the object could not be deserialised. + @treturn string A message describing why the JSON string is invalid. + @since 1.87.0 + @see textutils.json_null Use to serialize a JSON `null` value. + @see textutils.empty_json_array Use to serialize a JSON empty array. + @usage Unserialise a basic JSON object + + textutils.unserialiseJSON('{"name": "Steve", "age": null}') + + @usage Unserialise a basic JSON object, returning null values as @{json_null}. + + textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true }) + ]] unserialise_json = function(s, options) expect(1, s, "string") expect(2, options, "table", "nil") if options then field(options, "nbt_style", "boolean", "nil") - field(options, "nbt_style", "boolean", "nil") + field(options, "parse_null", "boolean", "nil") + field(options, "parse_empty_array", "boolean", "nil") else options = {} end diff --git a/src/test/resources/test-rom/spec/apis/textutils_spec.lua b/src/test/resources/test-rom/spec/apis/textutils_spec.lua index 71f426366..1bf3d7402 100644 --- a/src/test/resources/test-rom/spec/apis/textutils_spec.lua +++ b/src/test/resources/test-rom/spec/apis/textutils_spec.lua @@ -177,8 +177,15 @@ describe("The textutils library", function() expect(textutils.unserializeJSON("null", { parse_null = false })):eq(nil) end) - it("an empty array", function() - expect(textutils.unserializeJSON("[]", { parse_null = false })):eq(textutils.empty_json_array) + it("an empty array when parse_empty_array is true", function() + expect(textutils.unserializeJSON("[]")):eq(textutils.empty_json_array) + expect(textutils.unserializeJSON("[]", { parse_empty_array = true })):eq(textutils.empty_json_array) + end) + + it("an empty array when parse_empty_array is false", function() + expect(textutils.unserializeJSON("[]", { parse_empty_array = false })) + :ne(textutils.empty_json_array) + :same({}) end) it("basic objects", function() From a7536ea4fa86a912595ab48e1239421219842f18 Mon Sep 17 00:00:00 2001 From: Wojbie Date: Tue, 10 May 2022 22:30:10 +0200 Subject: [PATCH 18/22] Add important leading / Fixes #1094 --- src/main/resources/data/computercraft/lua/rom/programs/edit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/data/computercraft/lua/rom/programs/edit.lua b/src/main/resources/data/computercraft/lua/rom/programs/edit.lua index c913aa234..5331854d9 100644 --- a/src/main/resources/data/computercraft/lua/rom/programs/edit.lua +++ b/src/main/resources/data/computercraft/lua/rom/programs/edit.lua @@ -431,7 +431,7 @@ local tMenuFuncs = { file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath))) end) if ok then - local nTask = shell.openTab(sTempPath) + local nTask = shell.openTab("/" .. sTempPath) if nTask then shell.switchTab(nTask) else From f05a53944322bceafc45b647a5c97caa3c8d8b19 Mon Sep 17 00:00:00 2001 From: Fayne Aldan Date: Fri, 13 May 2022 13:41:52 -0600 Subject: [PATCH 19/22] Fix typo in documentation --- .../data/computercraft/lua/rom/apis/command/commands.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua index df2ae8f6d..55c20e207 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua @@ -12,7 +12,7 @@ instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. @{commands.async} provides a similar interface to execute asynchronous commands. `commands.async.say("Hi!")` is equivalent to -`commands.execAsync("Hi!")`. +`commands.execAsync("say Hi!")`. [mc]: https://minecraft.gamepedia.com/Commands From d631111610f9d7fc1fea8144877db3ef6236d312 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 19 May 2022 14:09:01 +0100 Subject: [PATCH 20/22] Improvements to contribution generation - Parse Co-authored-by lines too. There's several contributors (mostly via weblate, but a few GH ones too) who weren't credited otherwise. - Add support for git mailmap, remapping some emails to a canonnical username. This allows us to remove some duplicates - nobody needs both SquidDev and "Jonathan Coates." I'm not making this file public, as it contains email addresses. This does mean that CI builds will still contain the full list with duplicates. --- build.gradle | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 8c90c9e27..3ea9a6beb 100644 --- a/build.gradle +++ b/build.gradle @@ -220,9 +220,32 @@ processResources { try { hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim() - def blacklist = ['GitHub', 'dan200', 'Daniel Ratcliffe'] - ["git", "-C", projectDir, "log", "--format=tformat:%an%n%cn"].execute().text.split('\n').each { - if (!blacklist.contains(it)) contributors.add(it) + def blacklist = ['GitHub', 'Daniel Ratcliffe', 'Weblate'] + + // Extract all authors, commiters and co-authors from the git log. + def authors = ["git", "-C", projectDir, "log", "--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)"] + .execute().text.readLines().unique() + + // We now pass this through git's mailmap to de-duplicate some authors. + def remapAuthors = ["git", "check-mailmap", "--stdin"].execute() + remapAuthors.withWriter { stdin -> + if (stdin !instanceof BufferedWriter) stdin = new BufferedWriter(stdin) + + authors.forEach { + if (it == "") return + if (!it.endsWith(">")) it += ">" // Some commits have broken Co-Authored-By lines! + stdin.writeLine(it) + } + stdin.close() + } + + // And finally extract out the actual name. + def emailRegex = ~/^([^<]+) <.+>$/ + remapAuthors.text.readLines().forEach { + def matcher = it =~ emailRegex + matcher.find() + def name = matcher.group(1) + if (!blacklist.contains(name)) contributors.add(name) } } catch (Exception e) { e.printStackTrace() From 2639b84eb293b489d45ee053e582097c75c2d21f Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 22 May 2022 14:05:04 +0100 Subject: [PATCH 21/22] Deprecate IArguments.releaseImmediate This is only ever defined (and called) within the ILuaMachine-specific code. Not sure why I ever made this public. --- .../computercraft/api/lua/IArguments.java | 7 ++-- .../api/lua/ObjectArguments.java | 33 ------------------- .../computercraft/core/lua/BasicFunction.java | 5 ++- .../core/lua/ResultInterpreterFunction.java | 4 +-- .../computercraft/core/lua/TableImpl.java | 2 +- .../core/lua/VarargArguments.java | 19 +++++------ 6 files changed, 19 insertions(+), 51 deletions(-) diff --git a/src/main/java/dan200/computercraft/api/lua/IArguments.java b/src/main/java/dan200/computercraft/api/lua/IArguments.java index 0557665d6..731b00c45 100644 --- a/src/main/java/dan200/computercraft/api/lua/IArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/IArguments.java @@ -188,8 +188,8 @@ public interface IArguments * * Classes implementing this interface may choose to implement a more optimised version which does not copy the * table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that - * they do not access off the computer thread (and so should not be used with main-thread functions) or once the - * function call has finished (for instance, in callbacks). + * they do not access the table the computer thread (and so should not be used with main-thread functions) or once + * the initial call has finished (for instance, in a callback to {@link MethodResult#pullEvent}). * * @param index The argument number. * @return The argument's value. @@ -448,7 +448,10 @@ public interface IArguments * This is called when the current function finishes, before any main thread tasks have run. * * Called when the current function returns, and so some values are no longer guaranteed to be safe to access. + * + * @deprecated This method was an internal implementation detail and is no longer used. */ + @Deprecated default void releaseImmediate() { } diff --git a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java index 2fa9c8fe8..f6eddbbdb 100644 --- a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java @@ -5,12 +5,10 @@ */ package dan200.computercraft.api.lua; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; /** * An implementation of {@link IArguments} which wraps an array of {@link Object}. @@ -19,7 +17,6 @@ public final class ObjectArguments implements IArguments { private static final IArguments EMPTY = new ObjectArguments(); - private boolean released = false; private final List args; @Deprecated @@ -67,34 +64,4 @@ public final class ObjectArguments implements IArguments { return args.toArray(); } - - @Nonnull - @Override - public LuaTable getTableUnsafe( int index ) throws LuaException - { - if( released ) - { - throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); - } - - return IArguments.super.getTableUnsafe( index ); - } - - @Nonnull - @Override - public Optional> optTableUnsafe( int index ) throws LuaException - { - if( released ) - { - throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); - } - - return IArguments.super.optTableUnsafe( index ); - } - - @Override - public void releaseImmediate() - { - released = true; - } } diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index ee28bf8d5..b6a12ddec 100644 --- a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -6,7 +6,6 @@ package dan200.computercraft.core.lua; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.MethodResult; @@ -41,7 +40,7 @@ class BasicFunction extends VarArgFunction @Override public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError { - IArguments arguments = VarargArguments.of( args ); + VarargArguments arguments = VarargArguments.of( args ); MethodResult results; try { @@ -61,7 +60,7 @@ class BasicFunction extends VarArgFunction } finally { - arguments.releaseImmediate(); + arguments.close(); } if( results.getCallback() != null ) diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index e3652be3f..f848ebde2 100644 --- a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -51,7 +51,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction private void checkValid() { - if( arguments.released ) + if( arguments.closed ) { throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" ); } diff --git a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java index 73b799aea..a501bad3c 100644 --- a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java +++ b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java @@ -17,9 +17,9 @@ import java.util.Optional; final class VarargArguments implements IArguments { - private static final IArguments EMPTY = new VarargArguments( Constants.NONE ); + private static final VarargArguments EMPTY = new VarargArguments( Constants.NONE ); - boolean released; + boolean closed; private final Varargs varargs; private Object[] cache; @@ -28,7 +28,7 @@ final class VarargArguments implements IArguments this.varargs = varargs; } - static IArguments of( Varargs values ) + static VarargArguments of( Varargs values ) { return values == Constants.NONE ? EMPTY : new VarargArguments( values ); } @@ -109,9 +109,9 @@ final class VarargArguments implements IArguments @Override public dan200.computercraft.api.lua.LuaTable getTableUnsafe( int index ) throws LuaException { - if( released ) + if( closed ) { - throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); + throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been closed." ); } LuaValue value = varargs.arg( index + 1 ); @@ -123,9 +123,9 @@ final class VarargArguments implements IArguments @Override public Optional> optTableUnsafe( int index ) throws LuaException { - if( released ) + if( closed ) { - throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); + throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been closed." ); } LuaValue value = varargs.arg( index + 1 ); @@ -134,9 +134,8 @@ final class VarargArguments implements IArguments return Optional.of( new TableImpl( this, (LuaTable) value ) ); } - @Override - public void releaseImmediate() + public void close() { - released = true; + closed = true; } } From 431e4c9419bb62e3d3a0123c7389edb27a6073ff Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 22 May 2022 14:34:01 +0100 Subject: [PATCH 22/22] Some reformatting to config comments - Rewrap everything at 80 columns. To make this tolerable I'm using IDEA's language fragment support - hence the absurd line lengths. - Add full stops to all comments. - Clarify that HTTP rules are applied in-order. --- .../apis/http/options/AddressRuleConfig.java | 4 +- .../dan200/computercraft/shared/Config.java | 90 +++++++------------ 2 files changed, 35 insertions(+), 59 deletions(-) diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java index 69106a0e1..d9f0a95a5 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java @@ -32,10 +32,10 @@ public class AddressRuleConfig config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ); config.add( "timeout", AddressRule.TIMEOUT ); - config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." ); + config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request.\nNote that responses may receive more data than allowed, but this data will not\nbe returned to the client." ); config.set( "max_download", AddressRule.MAX_DOWNLOAD ); - config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." ); + config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This\nincludes headers and POST text." ); config.set( "max_upload", AddressRule.MAX_UPLOAD ); config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." ); diff --git a/src/main/java/dan200/computercraft/shared/Config.java b/src/main/java/dan200/computercraft/shared/Config.java index ff62e13a4..246bf7a21 100644 --- a/src/main/java/dan200/computercraft/shared/Config.java +++ b/src/main/java/dan200/computercraft/shared/Config.java @@ -98,12 +98,12 @@ public final class Config { // General computers computerSpaceLimit = builder - .comment( "The disk space limit for computers and turtles, in bytes" ) + .comment( "The disk space limit for computers and turtles, in bytes." ) .translation( TRANSLATION_PREFIX + "computer_space_limit" ) .define( "computer_space_limit", ComputerCraft.computerSpaceLimit ); floppySpaceLimit = builder - .comment( "The disk space limit for floppy disks, in bytes" ) + .comment( "The disk space limit for floppy disks, in bytes." ) .translation( TRANSLATION_PREFIX + "floppy_space_limit" ) .define( "floppy_space_limit", ComputerCraft.floppySpaceLimit ); @@ -113,49 +113,36 @@ public final class Config .defineInRange( "maximum_open_files", ComputerCraft.maximumFilesOpen, 0, Integer.MAX_VALUE ); disableLua51Features = builder - .comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. " + - "Useful for ensuring forward compatibility of your programs now." ) + .comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future\nupdate. Useful for ensuring forward compatibility of your programs now." ) .define( "disable_lua51_features", ComputerCraft.disableLua51Features ); defaultComputerSettings = builder - .comment( "A comma separated list of default system settings to set on new computers. Example: " + - "\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all " + - "autocompletion" ) + .comment( "A comma separated list of default system settings to set on new computers.\nExample: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwill disable all autocompletion." ) .define( "default_computer_settings", ComputerCraft.defaultComputerSettings ); logComputerErrors = builder - .comment( "Log exceptions thrown by peripherals and other Lua objects.\n" + - "This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." ) + .comment( "Log exceptions thrown by peripherals and other Lua objects. This makes it easier\nfor mod authors to debug problems, but may result in log spam should people use\nbuggy methods." ) .define( "log_computer_errors", ComputerCraft.logComputerErrors ); commandRequireCreative = builder - .comment( "Require players to be in creative mode and be opped in order to interact with command computers." + - "This is the default behaviour for vanilla's Command blocks." ) - .define( "command_require_creative", ComputerCraft.commandRequireCreative ); + .comment( "Require players to be in creative mode and be opped in order to interact with\ncommand computers. This is the default behaviour for vanilla's Command blocks." ).define( "command_require_creative", ComputerCraft.commandRequireCreative ); } { - builder.comment( "Controls execution behaviour of computers. This is largely intended for fine-tuning " + - "servers, and generally shouldn't need to be touched" ); + builder.comment( "Controls execution behaviour of computers. This is largely intended for\nfine-tuning servers, and generally shouldn't need to be touched." ); builder.push( "execution" ); computerThreads = builder - .comment( "Set the number of threads computers can run on. A higher number means more computers can run " + - "at once, but may induce lag.\n" + - "Please note that some mods may not work with a thread count higher than 1. Use with caution." ) + .comment( "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution." ) .worldRestart() .defineInRange( "computer_threads", ComputerCraft.computerThreads, 1, Integer.MAX_VALUE ); maxMainGlobalTime = builder - .comment( "The maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" + - "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " + - "- this aims to be the upper bound of the average time." ) + .comment( "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." ) .defineInRange( "max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ), 1, Integer.MAX_VALUE ); maxMainComputerTime = builder - .comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\n" + - "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " + - "- this aims to be the upper bound of the average time." ) + .comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." ) .defineInRange( "max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ), 1, Integer.MAX_VALUE ); builder.pop(); @@ -174,17 +161,14 @@ public final class Config .define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled ); httpRules = builder - .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or IPs.\n" + - "Each rule is an item with a 'host' to match against, and a series of properties. " + - "The host may be a domain name (\"pastebin.com\"),\n" + - "wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). If no rules, the domain is blocked." ) + .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule is an item with a 'host' to match against, and a series of\nproperties. Rules are evaluated in order, meaning earlier rules override later\nones.\nThe host may be a domain name (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or\nCIDR notation (\"127.0.0.0/8\").\nIf no rules, the domain is blocked." ) .defineList( "rules", Arrays.asList( AddressRuleConfig.makeRule( "$private", Action.DENY ), AddressRuleConfig.makeRule( "*", Action.ALLOW ) ), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) ); httpMaxRequests = builder - .comment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." ) + .comment( "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited." ) .defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE ); httpMaxWebsockets = builder @@ -192,15 +176,15 @@ public final class Config .defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE ); builder - .comment( "Limits bandwidth used by computers" ) + .comment( "Limits bandwidth used by computers." ) .push( "bandwidth" ); httpDownloadBandwidth = builder - .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)" ) + .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)." ) .defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE ); httpUploadBandwidth = builder - .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)" ) + .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)." ) .defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE ); builder.pop(); @@ -217,33 +201,27 @@ public final class Config .define( "command_block_enabled", ComputerCraft.enableCommandBlock ); modemRange = builder - .comment( "The range of Wireless Modems at low altitude in clear weather, in meters" ) + .comment( "The range of Wireless Modems at low altitude in clear weather, in meters." ) .defineInRange( "modem_range", ComputerCraft.modemRange, 0, MODEM_MAX_RANGE ); modemHighAltitudeRange = builder - .comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters" ) + .comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters." ) .defineInRange( "modem_high_altitude_range", ComputerCraft.modemHighAltitudeRange, 0, MODEM_MAX_RANGE ); modemRangeDuringStorm = builder - .comment( "The range of Wireless Modems at low altitude in stormy weather, in meters" ) + .comment( "The range of Wireless Modems at low altitude in stormy weather, in meters." ) .defineInRange( "modem_range_during_storm", ComputerCraft.modemRangeDuringStorm, 0, MODEM_MAX_RANGE ); modemHighAltitudeRangeDuringStorm = builder - .comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters" ) + .comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters." ) .defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modemHighAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE ); maxNotesPerTick = builder - .comment( "Maximum amount of notes a speaker can play at once" ) + .comment( "Maximum amount of notes a speaker can play at once." ) .defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE ); monitorBandwidth = builder - .comment( "The limit to how much monitor data can be sent *per tick*. Note:\n" + - " - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" + - " - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " + - "the same bandwidth limit as sending to 20.\n" + - " - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " + - "in a single tick. \n" + - "Set to 0 to disable." ) + .comment( "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable." ) .defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE ); builder.pop(); @@ -254,23 +232,24 @@ public final class Config builder.push( "turtle" ); turtlesNeedFuel = builder - .comment( "Set whether Turtles require fuel to move" ) + .comment( "Set whether Turtles require fuel to move." ) .define( "need_fuel", ComputerCraft.turtlesNeedFuel ); turtleFuelLimit = builder - .comment( "The fuel limit for Turtles" ) + .comment( "The fuel limit for Turtles." ) .defineInRange( "normal_fuel_limit", ComputerCraft.turtleFuelLimit, 0, Integer.MAX_VALUE ); advancedTurtleFuelLimit = builder - .comment( "The fuel limit for Advanced Turtles" ) + .comment( "The fuel limit for Advanced Turtles." ) .defineInRange( "advanced_fuel_limit", ComputerCraft.advancedTurtleFuelLimit, 0, Integer.MAX_VALUE ); turtlesObeyBlockProtection = builder - .comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas (such as near the server spawn point)" ) + .comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas\n(such as near the server spawn point)." ) .define( "obey_block_protection", ComputerCraft.turtlesObeyBlockProtection ); turtlesCanPush = builder - .comment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" ) + .comment( "If set to true, Turtles will push entities out of the way instead of stopping if\n" + + "there is space to do so." ) .define( "can_push", ComputerCraft.turtlesCanPush ); turtleDisabledActions = builder @@ -281,20 +260,19 @@ public final class Config } { - builder.comment( "Configure the size of various computer's terminals.\n" + - "Larger terminals require more bandwidth, so use with care." ).push( "term_sizes" ); + builder.comment( "Configure the size of various computer's terminals.\nLarger terminals require more bandwidth, so use with care." ).push( "term_sizes" ); - builder.comment( "Terminal size of computers" ).push( "computer" ); + builder.comment( "Terminal size of computers." ).push( "computer" ); computerTermWidth = builder.defineInRange( "width", ComputerCraft.computerTermWidth, 1, 255 ); computerTermHeight = builder.defineInRange( "height", ComputerCraft.computerTermHeight, 1, 255 ); builder.pop(); - builder.comment( "Terminal size of pocket computers" ).push( "pocket_computer" ); + builder.comment( "Terminal size of pocket computers." ).push( "pocket_computer" ); pocketTermWidth = builder.defineInRange( "width", ComputerCraft.pocketTermWidth, 1, 255 ); pocketTermHeight = builder.defineInRange( "height", ComputerCraft.pocketTermHeight, 1, 255 ); builder.pop(); - builder.comment( "Maximum size of monitors (in blocks)" ).push( "monitor" ); + builder.comment( "Maximum size of monitors (in blocks)." ).push( "monitor" ); monitorWidth = builder.defineInRange( "width", ComputerCraft.monitorWidth, 1, 32 ); monitorHeight = builder.defineInRange( "height", ComputerCraft.monitorHeight, 1, 32 ); builder.pop(); @@ -306,12 +284,10 @@ public final class Config Builder clientBuilder = new Builder(); monitorRenderer = clientBuilder - .comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if " + - "monitors have performance issues, you may wish to experiment with alternative renderers." ) + .comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers." ) .defineEnum( "monitor_renderer", MonitorRenderer.BEST ); monitorDistance = clientBuilder - .comment( "The maximum distance monitors will render at. This defaults to the standard tile entity limit, " + - "but may be extended if you wish to build larger monitors." ) + .comment( "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors." ) .defineInRange( "monitor_distance", 64, 16, 1024 ); clientSpec = clientBuilder.build(); }
Default Colors